packages feed

ghcide 0.3.0 → 0.4.0

raw patch · 45 files changed

+2078/−788 lines, 45 filesdep +implicit-hie-cradledep +record-dot-preprocessordep +record-hasfielddep ~ghc-checkdep ~hie-biosdep ~lsp-testPVP ok

version bump matches the API change (PVP)

Dependencies added: implicit-hie-cradle, record-dot-preprocessor, record-hasfield

Dependency ranges changed: ghc-check, hie-bios, lsp-test

API changes (from Hackage documentation)

- Development.IDE.Core.FileStore: setBufferModified :: IdeState -> NormalizedFilePath -> Maybe Text -> IO ()
- Development.IDE.Core.Rules: instance GHC.Show.Show Development.IDE.Core.Rules.GenerateInterfaceFiles
- Development.IDE.Core.Shake: instance GHC.Show.Show (Development.IDE.Core.Shake.DelayedAction a)
- Development.IDE.GHC.Compat: HsValBinds :: XHsValBinds idL idR -> HsValBindsLR idL idR -> HsLocalBindsLR idL idR
- Development.IDE.GHC.Compat: ValBinds :: XValBinds idL idR -> LHsBindsLR idL idR -> [LSig idR] -> HsValBindsLR idL idR
- Development.IDE.GHC.Compat: type HsValBinds id = HsValBindsLR id id
- Development.IDE.Plugin.CodeAction: executeAddSignatureCommand :: LspFuncs c -> IdeState -> ExecuteCommandParams -> IO (Either ResponseError Value, Maybe (ServerMethod, ApplyWorkspaceEditParams))
+ Development.IDE: Debug :: Priority
+ Development.IDE: Diagnostic :: Range -> Maybe DiagnosticSeverity -> Maybe NumberOrString -> Maybe DiagnosticSource -> Text -> Maybe (List DiagnosticTag) -> Maybe (List DiagnosticRelatedInformation) -> Diagnostic
+ Development.IDE: DsError :: DiagnosticSeverity
+ Development.IDE: DsHint :: DiagnosticSeverity
+ Development.IDE: DsInfo :: DiagnosticSeverity
+ Development.IDE: DsWarning :: DiagnosticSeverity
+ Development.IDE: Error :: Priority
+ Development.IDE: FastResult :: Maybe (a, PositionMapping) -> IO (Maybe a) -> FastResult a
+ Development.IDE: GenerateByteCode :: GenerateByteCode
+ Development.IDE: GenerateCore :: GenerateCore
+ Development.IDE: GetDependencies :: GetDependencies
+ Development.IDE: GetDependencyInformation :: GetDependencyInformation
+ Development.IDE: GetKnownTargets :: GetKnownTargets
+ Development.IDE: GetLocatedImports :: GetLocatedImports
+ Development.IDE: GetModIface :: GetModIface
+ Development.IDE: GetModIfaceFromDisk :: GetModIfaceFromDisk
+ Development.IDE: GetModSummary :: GetModSummary
+ Development.IDE: GetModSummaryWithoutTimestamps :: GetModSummaryWithoutTimestamps
+ Development.IDE: GetModuleGraph :: GetModuleGraph
+ Development.IDE: GetParsedModule :: GetParsedModule
+ Development.IDE: GetSpanInfo :: GetSpanInfo
+ Development.IDE: GhcSession :: GhcSession
+ Development.IDE: GhcSessionDeps :: GhcSessionDeps
+ Development.IDE: GhcSessionIO :: GhcSessionIO
+ Development.IDE: HiFileResult :: !ModSummary -> !ModIface -> HiFileResult
+ Development.IDE: HideDiag :: ShowDiagnostic
+ Development.IDE: IdeAction :: ReaderT ShakeExtras IO a -> IdeAction a
+ Development.IDE: IdeConfiguration :: HashSet NormalizedUri -> IdeConfiguration
+ Development.IDE: Info :: Priority
+ Development.IDE: IsFOI :: FileOfInterestStatus -> IsFileOfInterestResult
+ Development.IDE: IsFileOfInterest :: IsFileOfInterest
+ Development.IDE: List :: [a] -> List a
+ Development.IDE: Location :: Uri -> Range -> Location
+ Development.IDE: Logger :: (Priority -> Text -> IO ()) -> Logger
+ Development.IDE: Modified :: FileOfInterestStatus
+ Development.IDE: NotFOI :: IsFileOfInterestResult
+ Development.IDE: OnDisk :: FileOfInterestStatus
+ Development.IDE: PFailed :: (DynFlags -> Messages) -> SrcSpan -> MsgDoc -> ParseResult a
+ Development.IDE: POk :: PState -> a -> ParseResult a
+ Development.IDE: Plugin :: Rules () -> PartialHandlers c -> Plugin c
+ Development.IDE: Position :: Int -> Int -> Position
+ Development.IDE: Range :: Position -> Position -> Range
+ Development.IDE: ReportImportCycles :: ReportImportCycles
+ Development.IDE: ShowDiag :: ShowDiagnostic
+ Development.IDE: TcModuleResult :: TypecheckedModule -> HomeModInfo -> !Bool -> TcModuleResult
+ Development.IDE: Telemetry :: Priority
+ Development.IDE: TypeCheck :: TypeCheck
+ Development.IDE: Uri :: Text -> Uri
+ Development.IDE: Warning :: Priority
+ Development.IDE: [$sel:_code:Diagnostic] :: Diagnostic -> Maybe NumberOrString
+ Development.IDE: [$sel:_message:Diagnostic] :: Diagnostic -> Text
+ Development.IDE: [$sel:_range:Diagnostic] :: Diagnostic -> Range
+ Development.IDE: [$sel:_relatedInformation:Diagnostic] :: Diagnostic -> Maybe (List DiagnosticRelatedInformation)
+ Development.IDE: [$sel:_severity:Diagnostic] :: Diagnostic -> Maybe DiagnosticSeverity
+ Development.IDE: [$sel:_source:Diagnostic] :: Diagnostic -> Maybe DiagnosticSource
+ Development.IDE: [$sel:_tags:Diagnostic] :: Diagnostic -> Maybe (List DiagnosticTag)
+ Development.IDE: [$sel:workspaceFolders:IdeConfiguration] :: IdeConfiguration -> HashSet NormalizedUri
+ Development.IDE: [_character] :: Position -> Int
+ Development.IDE: [_end] :: Range -> Position
+ Development.IDE: [_line] :: Position -> Int
+ Development.IDE: [_range] :: Location -> Range
+ Development.IDE: [_start] :: Range -> Position
+ Development.IDE: [_uri] :: Location -> Uri
+ Development.IDE: [getUri] :: Uri -> Text
+ Development.IDE: [hirModIface] :: HiFileResult -> !ModIface
+ Development.IDE: [hirModSummary] :: HiFileResult -> !ModSummary
+ Development.IDE: [logPriority] :: Logger -> Priority -> Text -> IO ()
+ Development.IDE: [pluginHandler] :: Plugin c -> PartialHandlers c
+ Development.IDE: [pluginRules] :: Plugin c -> Rules ()
+ Development.IDE: [runIdeActionT] :: IdeAction a -> ReaderT ShakeExtras IO a
+ Development.IDE: [stale] :: FastResult a -> Maybe (a, PositionMapping)
+ Development.IDE: [tmrDeferedError] :: TcModuleResult -> !Bool
+ Development.IDE: [tmrModInfo] :: TcModuleResult -> HomeModInfo
+ Development.IDE: [tmrModule] :: TcModuleResult -> TypecheckedModule
+ Development.IDE: [uptoDate] :: FastResult a -> IO (Maybe a)
+ Development.IDE: action :: Partial => Action a -> Rules ()
+ Development.IDE: actionLogger :: Action Logger
+ Development.IDE: bytestringToStringBuffer :: ByteString -> StringBuffer
+ Development.IDE: catchSrcErrors :: (HasDynFlags m, ExceptionMonad m) => Text -> m a -> m (Either [FileDiagnostic] a)
+ Development.IDE: cgGutsToCoreModule :: SafeHaskellMode -> CgGuts -> ModDetails -> CoreModule
+ Development.IDE: codeActionPlugin :: (LspFuncs c -> IdeState -> TextDocumentIdentifier -> Range -> CodeActionContext -> IO (Either ResponseError [CAResult])) -> Plugin c
+ Development.IDE: codeActionPluginWithRules :: Rules () -> (LspFuncs c -> IdeState -> TextDocumentIdentifier -> Range -> CodeActionContext -> IO (Either ResponseError [CAResult])) -> Plugin c
+ Development.IDE: data Action a
+ Development.IDE: data Diagnostic
+ Development.IDE: data DiagnosticSeverity
+ Development.IDE: data FastResult a
+ Development.IDE: data FileOfInterestStatus
+ Development.IDE: data GenerateByteCode
+ Development.IDE: data GenerateCore
+ Development.IDE: data GetDependencies
+ Development.IDE: data GetDependencyInformation
+ Development.IDE: data GetKnownTargets
+ Development.IDE: data GetLocatedImports
+ Development.IDE: data GetModIface
+ Development.IDE: data GetModIfaceFromDisk
+ Development.IDE: data GetModSummary
+ Development.IDE: data GetModSummaryWithoutTimestamps
+ Development.IDE: data GetModificationTime
+ Development.IDE: data GetModuleGraph
+ Development.IDE: data GetParsedModule
+ Development.IDE: data GetSpanInfo
+ Development.IDE: data GhcSession
+ Development.IDE: data GhcSessionDeps
+ Development.IDE: data GhcSessionIO
+ Development.IDE: data HiFileResult
+ Development.IDE: data HscEnvEq
+ Development.IDE: data IdeConfiguration
+ Development.IDE: data IdeState
+ Development.IDE: data IsFileOfInterest
+ Development.IDE: data IsFileOfInterestResult
+ Development.IDE: data Location
+ Development.IDE: data Logger
+ Development.IDE: data NormalizedFilePath
+ Development.IDE: data NormalizedUri
+ Development.IDE: data ParseResult a
+ Development.IDE: data Plugin c
+ Development.IDE: data Position
+ Development.IDE: data Priority
+ Development.IDE: data Range
+ Development.IDE: data ReportImportCycles
+ Development.IDE: data Rules a
+ Development.IDE: data ShakeExtras
+ Development.IDE: data ShowDiagnostic
+ Development.IDE: data TcModuleResult
+ Development.IDE: data TypeCheck
+ Development.IDE: define :: IdeRule k v => (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()
+ Development.IDE: defineEarlyCutoff :: IdeRule k v => (k -> NormalizedFilePath -> Action (Maybe ByteString, IdeResult v)) -> Rules ()
+ Development.IDE: diagFromErrMsg :: Text -> DynFlags -> ErrMsg -> [FileDiagnostic]
+ Development.IDE: diagFromErrMsgs :: Text -> DynFlags -> Bag ErrMsg -> [FileDiagnostic]
+ Development.IDE: diagFromGhcException :: Text -> DynFlags -> GhcException -> [FileDiagnostic]
+ Development.IDE: diagFromString :: Text -> DiagnosticSeverity -> SrcSpan -> String -> [FileDiagnostic]
+ Development.IDE: diagFromStrings :: Text -> DiagnosticSeverity -> [(SrcSpan, String)] -> [FileDiagnostic]
+ Development.IDE: disableWarningsAsErrors :: DynFlags -> DynFlags
+ Development.IDE: dontWriteHieFiles :: DynFlags -> DynFlags
+ Development.IDE: emptyFilePath :: NormalizedFilePath
+ Development.IDE: emptyPathUri :: NormalizedUri
+ Development.IDE: evalGhcEnv :: HscEnv -> Ghc b -> IO b
+ Development.IDE: filePathToUri' :: NormalizedFilePath -> NormalizedUri
+ Development.IDE: fingerprintFromStringBuffer :: StringBuffer -> IO Fingerprint
+ Development.IDE: fingerprintToBS :: Fingerprint -> ByteString
+ Development.IDE: fromNormalizedFilePath :: NormalizedFilePath -> FilePath
+ Development.IDE: fromNormalizedUri :: NormalizedUri -> Uri
+ Development.IDE: fromUri :: NormalizedUri -> NormalizedFilePath
+ Development.IDE: getAtPoint :: NormalizedFilePath -> Position -> IdeAction (Maybe (Maybe Range, [Text]))
+ Development.IDE: getDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe Location)
+ Development.IDE: getFileContents :: NormalizedFilePath -> Action (UTCTime, Maybe Text)
+ Development.IDE: getFileExists :: NormalizedFilePath -> Action Bool
+ Development.IDE: getFilesOfInterest :: Action (HashMap NormalizedFilePath FileOfInterestStatus)
+ Development.IDE: getParsedModule :: NormalizedFilePath -> Action (Maybe ParsedModule)
+ Development.IDE: getPid :: IO Text
+ Development.IDE: getTypeDefinition :: NormalizedFilePath -> Position -> IdeAction (Maybe [Location])
+ Development.IDE: hDuplicateTo' :: Handle -> Handle -> IO ()
+ Development.IDE: hiFileFingerPrint :: HiFileResult -> ByteString
+ Development.IDE: hscEnvWithImportPaths :: HscEnvEq -> HscEnv
+ Development.IDE: ideErrorText :: NormalizedFilePath -> Text -> FileDiagnostic
+ Development.IDE: ideErrorWithSource :: Maybe DiagnosticSource -> Maybe DiagnosticSeverity -> a -> Text -> (a, ShowDiagnostic, Diagnostic)
+ Development.IDE: ideLogger :: IdeState -> Logger
+ Development.IDE: isInsideSrcSpan :: Position -> SrcSpan -> Bool
+ Development.IDE: isWorkspaceFile :: NormalizedFilePath -> Action Bool
+ Development.IDE: logDebug :: Logger -> Text -> IO ()
+ Development.IDE: logError :: Logger -> Text -> IO ()
+ Development.IDE: logInfo :: Logger -> Text -> IO ()
+ Development.IDE: logTelemetry :: Logger -> Text -> IO ()
+ Development.IDE: logWarning :: Logger -> Text -> IO ()
+ Development.IDE: lookupPackageConfig :: UnitId -> HscEnv -> Maybe PackageConfig
+ Development.IDE: makeLspCommandId :: Text -> IO Text
+ Development.IDE: modifyDynFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m ()
+ Development.IDE: moduleImportPath :: NormalizedFilePath -> ModuleName -> Maybe FilePath
+ Development.IDE: newHscEnvEq :: FilePath -> HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq
+ Development.IDE: newHscEnvEqPreserveImportPaths :: HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq
+ Development.IDE: newtype IdeAction a
+ Development.IDE: newtype List a
+ Development.IDE: newtype Uri
+ Development.IDE: noFilePath :: FilePath
+ Development.IDE: noLogging :: Logger
+ Development.IDE: noRange :: Range
+ Development.IDE: noSpan :: String -> SrcSpan
+ Development.IDE: pattern GetModificationTime :: GetModificationTime
+ Development.IDE: prettyPrint :: Outputable a => a -> String
+ Development.IDE: printName :: Name -> String
+ Development.IDE: printRdrName :: RdrName -> String
+ Development.IDE: readFileUtf8 :: FilePath -> IO Text
+ Development.IDE: readSrcSpan :: ReadS RealSrcSpan
+ Development.IDE: realSpan :: SrcSpan -> Maybe RealSrcSpan
+ Development.IDE: realSrcSpanToRange :: RealSrcSpan -> Range
+ Development.IDE: runAction :: String -> IdeState -> Action a -> IO a
+ Development.IDE: runGhcEnv :: HscEnv -> Ghc a -> IO (HscEnv, a)
+ Development.IDE: runIdeAction :: String -> ShakeExtras -> IdeAction a -> IO a
+ Development.IDE: runParser :: DynFlags -> String -> P a -> ParseResult a
+ Development.IDE: setHieDir :: FilePath -> DynFlags -> DynFlags
+ Development.IDE: showDiagnostics :: [FileDiagnostic] -> Text
+ Development.IDE: showDiagnosticsColored :: [FileDiagnostic] -> Text
+ Development.IDE: showPosition :: Position -> String
+ Development.IDE: srcSpanToFilename :: SrcSpan -> Maybe FilePath
+ Development.IDE: srcSpanToLocation :: SrcSpan -> Maybe Location
+ Development.IDE: srcSpanToRange :: SrcSpan -> Maybe Range
+ Development.IDE: stringBufferToByteString :: StringBuffer -> ByteString
+ Development.IDE: textToStringBuffer :: Text -> StringBuffer
+ Development.IDE: tmrModSummary :: TcModuleResult -> ModSummary
+ Development.IDE: tmr_hiFileResult :: TcModuleResult -> HiFileResult
+ Development.IDE: toDSeverity :: Severity -> Maybe DiagnosticSeverity
+ Development.IDE: toNormalizedFilePath' :: FilePath -> NormalizedFilePath
+ Development.IDE: toNormalizedUri :: Uri -> NormalizedUri
+ Development.IDE: type DiagnosticStore = HashMap NormalizedUri StoreItem
+ Development.IDE: type FileDiagnostic = (NormalizedFilePath, ShowDiagnostic, Diagnostic)
+ Development.IDE: type IdeResult v = ([FileDiagnostic], Maybe v)
+ Development.IDE: type IdeRule k v = (RuleResult k ~ v, ShakeValue k, Show v, Typeable v, NFData v)
+ Development.IDE: type family RuleResult key :: Type
+ Development.IDE: uriToFilePath' :: Uri -> Maybe FilePath
+ Development.IDE: use :: IdeRule k v => k -> NormalizedFilePath -> Action (Maybe v)
+ Development.IDE: useNoFile :: IdeRule k v => k -> Action (Maybe v)
+ Development.IDE: useNoFile_ :: IdeRule k v => k -> Action v
+ Development.IDE: useWithStale :: IdeRule k v => k -> NormalizedFilePath -> Action (Maybe (v, PositionMapping))
+ Development.IDE: useWithStaleFast :: IdeRule k v => k -> NormalizedFilePath -> IdeAction (Maybe (v, PositionMapping))
+ Development.IDE: useWithStaleFast' :: IdeRule k v => k -> NormalizedFilePath -> IdeAction (FastResult v)
+ Development.IDE: useWithStale_ :: IdeRule k v => k -> NormalizedFilePath -> Action (v, PositionMapping)
+ Development.IDE: use_ :: IdeRule k v => k -> NormalizedFilePath -> Action v
+ Development.IDE: uses :: IdeRule k v => k -> [NormalizedFilePath] -> Action [Maybe v]
+ Development.IDE: uses_ :: IdeRule k v => k -> [NormalizedFilePath] -> Action [v]
+ Development.IDE: zeroSpan :: FastString -> RealSrcSpan
+ Development.IDE.Core.FileStore: isFileOfInterestRule :: Rules ()
+ Development.IDE.Core.FileStore: typecheckParents :: IdeState -> NormalizedFilePath -> IO ()
+ Development.IDE.Core.OfInterest: Modified :: FileOfInterestStatus
+ Development.IDE.Core.OfInterest: OnDisk :: FileOfInterestStatus
+ Development.IDE.Core.OfInterest: data FileOfInterestStatus
+ Development.IDE.Core.PositionMapping: PositionExact :: !a -> PositionResult a
+ Development.IDE.Core.PositionMapping: PositionRange :: a -> a -> PositionResult a
+ Development.IDE.Core.PositionMapping: [unsafeLowerRange] :: PositionResult a -> a
+ Development.IDE.Core.PositionMapping: [unsafeUpperRange] :: PositionResult a -> a
+ Development.IDE.Core.PositionMapping: data PositionResult a
+ Development.IDE.Core.PositionMapping: instance GHC.Base.Applicative Development.IDE.Core.PositionMapping.PositionResult
+ Development.IDE.Core.PositionMapping: instance GHC.Base.Functor Development.IDE.Core.PositionMapping.PositionResult
+ Development.IDE.Core.PositionMapping: instance GHC.Base.Monad Development.IDE.Core.PositionMapping.PositionResult
+ Development.IDE.Core.PositionMapping: instance GHC.Classes.Eq a => GHC.Classes.Eq (Development.IDE.Core.PositionMapping.PositionResult a)
+ Development.IDE.Core.PositionMapping: instance GHC.Classes.Ord a => GHC.Classes.Ord (Development.IDE.Core.PositionMapping.PositionResult a)
+ Development.IDE.Core.PositionMapping: instance GHC.Show.Show a => GHC.Show.Show (Development.IDE.Core.PositionMapping.PositionResult a)
+ Development.IDE.Core.PositionMapping: lowerRange :: PositionResult a -> a
+ Development.IDE.Core.PositionMapping: positionResultToMaybe :: PositionResult a -> Maybe a
+ Development.IDE.Core.PositionMapping: upperRange :: PositionResult a -> a
+ Development.IDE.Core.RuleTypes: GetKnownTargets :: GetKnownTargets
+ Development.IDE.Core.RuleTypes: GetModuleGraph :: GetModuleGraph
+ Development.IDE.Core.RuleTypes: IsFOI :: FileOfInterestStatus -> IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: Modified :: FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: NotFOI :: IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: OnDisk :: FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: [tmrDeferedError] :: TcModuleResult -> !Bool
+ Development.IDE.Core.RuleTypes: data FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: data GetKnownTargets
+ Development.IDE.Core.RuleTypes: data GetModuleGraph
+ Development.IDE.Core.RuleTypes: data IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: instance Control.DeepSeq.NFData Development.IDE.Core.RuleTypes.FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: instance Control.DeepSeq.NFData Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance Control.DeepSeq.NFData Development.IDE.Core.RuleTypes.GetModuleGraph
+ Development.IDE.Core.RuleTypes: instance Control.DeepSeq.NFData Development.IDE.Core.RuleTypes.IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: instance Data.Binary.Class.Binary Development.IDE.Core.RuleTypes.FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: instance Data.Binary.Class.Binary Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance Data.Binary.Class.Binary Development.IDE.Core.RuleTypes.GetModuleGraph
+ Development.IDE.Core.RuleTypes: instance Data.Binary.Class.Binary Development.IDE.Core.RuleTypes.IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: instance Data.Hashable.Class.Hashable Development.IDE.Core.RuleTypes.FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: instance Data.Hashable.Class.Hashable Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance Data.Hashable.Class.Hashable Development.IDE.Core.RuleTypes.GetModuleGraph
+ Development.IDE.Core.RuleTypes: instance Data.Hashable.Class.Hashable Development.IDE.Core.RuleTypes.IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Eq Development.IDE.Core.RuleTypes.FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Eq Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Eq Development.IDE.Core.RuleTypes.GetModuleGraph
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Eq Development.IDE.Core.RuleTypes.IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Ord Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance GHC.Generics.Generic Development.IDE.Core.RuleTypes.FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: instance GHC.Generics.Generic Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance GHC.Generics.Generic Development.IDE.Core.RuleTypes.GetModuleGraph
+ Development.IDE.Core.RuleTypes: instance GHC.Generics.Generic Development.IDE.Core.RuleTypes.IsFileOfInterestResult
+ Development.IDE.Core.RuleTypes: instance GHC.Show.Show Development.IDE.Core.RuleTypes.FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: instance GHC.Show.Show Development.IDE.Core.RuleTypes.GetKnownTargets
+ Development.IDE.Core.RuleTypes: instance GHC.Show.Show Development.IDE.Core.RuleTypes.GetModuleGraph
+ Development.IDE.Core.RuleTypes: instance GHC.Show.Show Development.IDE.Core.RuleTypes.IsFileOfInterestResult
+ Development.IDE.Core.Shake: [actionQueue] :: ShakeExtras -> ActionQueue
+ Development.IDE.Core.Shake: [exportsMap] :: ShakeExtras -> Var ExportsMap
+ Development.IDE.Core.Shake: [knownTargetsVar] :: ShakeExtras -> Var (Hashed KnownTargets)
+ Development.IDE.Core.Shake: knownTargets :: Action (Hashed KnownTargets)
+ Development.IDE.Core.Shake: toKnownFiles :: KnownTargets -> HashSet NormalizedFilePath
+ Development.IDE.Core.Shake: type KnownTargets = HashMap ModuleName [NormalizedFilePath]
+ Development.IDE.GHC.Compat: addBootSuffixLocnOut :: ModLocation -> ModLocation
+ Development.IDE.GHC.Compat: applyPluginsParsedResultAction :: HscEnv -> DynFlags -> ModSummary -> ApiAnns -> ParsedSource -> IO ParsedSource
+ Development.IDE.GHC.Compat: disableWarningsAsErrors :: DynFlags -> DynFlags
+ Development.IDE.GHC.Compat: initializePlugins :: HscEnv -> DynFlags -> IO DynFlags
+ Development.IDE.GHC.Compat: pattern HsValBinds :: HsValBindsLR p p -> HsLocalBindsLR p p
+ Development.IDE.GHC.Compat: pattern ValBinds :: LHsBinds p -> [LSig p] -> HsValBindsLR p p
+ Development.IDE.GHC.Compat: setUpTypedHoles :: DynFlags -> DynFlags
+ Development.IDE.GHC.Error: realSrcSpanToRange :: RealSrcSpan -> Range
+ Development.IDE.GHC.Util: disableWarningsAsErrors :: DynFlags -> DynFlags
+ Development.IDE.GHC.Util: envImportPaths :: HscEnvEq -> Maybe [String]
+ Development.IDE.GHC.Util: hscEnvWithImportPaths :: HscEnvEq -> HscEnv
+ Development.IDE.GHC.Util: newHscEnvEqPreserveImportPaths :: HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq
+ Development.IDE.Import.DependencyInformation: [depReverseModuleDeps] :: DependencyInformation -> !IntMap IntSet
+ Development.IDE.Import.DependencyInformation: reverseDependencies :: NormalizedFilePath -> DependencyInformation -> [NormalizedFilePath]
+ Development.IDE.Plugin.CodeAction: blockCommandId :: Text
+ Development.IDE.Plugin.CodeAction: commandHandler :: LspFuncs c -> IdeState -> ExecuteCommandParams -> IO (Either ResponseError Value, Maybe (ServerMethod, ApplyWorkspaceEditParams))
+ Development.IDE.Plugin.CodeAction: typeSignatureCommandId :: Text
+ Development.IDE.Plugin.Completions: getCompletionsLSP :: LspFuncs c -> IdeState -> CompletionParams -> IO (Either ResponseError CompletionResponseResult)
+ Development.IDE.Plugin.Test: BlockSeconds :: Seconds -> TestRequest
+ Development.IDE.Plugin.Test: GetInterfaceFilesDir :: FilePath -> TestRequest
+ Development.IDE.Plugin.Test: GetShakeSessionQueueCount :: TestRequest
+ Development.IDE.Plugin.Test: data TestRequest
+ Development.IDE.Plugin.Test: instance Data.Aeson.Types.FromJSON.FromJSON Development.IDE.Plugin.Test.TestRequest
+ Development.IDE.Plugin.Test: instance Data.Aeson.Types.ToJSON.ToJSON Development.IDE.Plugin.Test.TestRequest
+ Development.IDE.Plugin.Test: instance GHC.Generics.Generic Development.IDE.Plugin.Test.TestRequest
+ Development.IDE.Plugin.Test: plugin :: Plugin c
+ Development.IDE.Types.Exports: ExportsMap :: HashMap IdentifierText (HashSet (IdentInfo, ModuleNameText)) -> ExportsMap
+ Development.IDE.Types.Exports: IdentInfo :: !Text -> Text -> !Maybe Text -> !Bool -> IdentInfo
+ Development.IDE.Types.Exports: [getExportsMap] :: ExportsMap -> HashMap IdentifierText (HashSet (IdentInfo, ModuleNameText))
+ Development.IDE.Types.Exports: [isDatacon] :: IdentInfo -> !Bool
+ Development.IDE.Types.Exports: [name] :: IdentInfo -> !Text
+ Development.IDE.Types.Exports: [parent] :: IdentInfo -> !Maybe Text
+ Development.IDE.Types.Exports: [rendered] :: IdentInfo -> Text
+ Development.IDE.Types.Exports: createExportsMap :: [ModIface] -> ExportsMap
+ Development.IDE.Types.Exports: data IdentInfo
+ Development.IDE.Types.Exports: instance Control.DeepSeq.NFData Development.IDE.Types.Exports.ExportsMap
+ Development.IDE.Types.Exports: instance Control.DeepSeq.NFData Development.IDE.Types.Exports.IdentInfo
+ Development.IDE.Types.Exports: instance Data.Hashable.Class.Hashable Development.IDE.Types.Exports.IdentInfo
+ Development.IDE.Types.Exports: instance GHC.Base.Monoid Development.IDE.Types.Exports.ExportsMap
+ Development.IDE.Types.Exports: instance GHC.Base.Semigroup Development.IDE.Types.Exports.ExportsMap
+ Development.IDE.Types.Exports: instance GHC.Classes.Eq Development.IDE.Types.Exports.IdentInfo
+ Development.IDE.Types.Exports: instance GHC.Generics.Generic Development.IDE.Types.Exports.IdentInfo
+ Development.IDE.Types.Exports: instance GHC.Show.Show Development.IDE.Types.Exports.ExportsMap
+ Development.IDE.Types.Exports: instance GHC.Show.Show Development.IDE.Types.Exports.IdentInfo
+ Development.IDE.Types.Exports: newtype ExportsMap
+ Development.IDE.Types.Options: AlwaysCheck :: CheckParents
+ Development.IDE.Types.Options: CheckOnClose :: CheckParents
+ Development.IDE.Types.Options: CheckOnSaveAndClose :: CheckParents
+ Development.IDE.Types.Options: CheckProject :: Bool -> CheckProject
+ Development.IDE.Types.Options: HaddockParse :: OptHaddockParse
+ Development.IDE.Types.Options: LspConfig :: CheckParents -> CheckProject -> LspConfig
+ Development.IDE.Types.Options: NeverCheck :: CheckParents
+ Development.IDE.Types.Options: NoHaddockParse :: OptHaddockParse
+ Development.IDE.Types.Options: [checkParents] :: LspConfig -> CheckParents
+ Development.IDE.Types.Options: [checkProject] :: LspConfig -> CheckProject
+ Development.IDE.Types.Options: [optCheckParents] :: IdeOptions -> CheckParents
+ Development.IDE.Types.Options: [optCheckProject] :: IdeOptions -> CheckProject
+ Development.IDE.Types.Options: [optCustomDynFlags] :: IdeOptions -> DynFlags -> DynFlags
+ Development.IDE.Types.Options: [optHaddockParse] :: IdeOptions -> OptHaddockParse
+ Development.IDE.Types.Options: [shouldCheckProject] :: CheckProject -> Bool
+ Development.IDE.Types.Options: data CheckParents
+ Development.IDE.Types.Options: data LspConfig
+ Development.IDE.Types.Options: data OptHaddockParse
+ Development.IDE.Types.Options: defaultLspConfig :: LspConfig
+ Development.IDE.Types.Options: instance Data.Aeson.Types.FromJSON.FromJSON Development.IDE.Types.Options.CheckParents
+ Development.IDE.Types.Options: instance Data.Aeson.Types.FromJSON.FromJSON Development.IDE.Types.Options.CheckProject
+ Development.IDE.Types.Options: instance Data.Aeson.Types.FromJSON.FromJSON Development.IDE.Types.Options.LspConfig
+ Development.IDE.Types.Options: instance Data.Aeson.Types.ToJSON.ToJSON Development.IDE.Types.Options.CheckParents
+ Development.IDE.Types.Options: instance Data.Aeson.Types.ToJSON.ToJSON Development.IDE.Types.Options.CheckProject
+ Development.IDE.Types.Options: instance Data.Aeson.Types.ToJSON.ToJSON Development.IDE.Types.Options.LspConfig
+ Development.IDE.Types.Options: instance GHC.Classes.Eq Development.IDE.Types.Options.CheckParents
+ Development.IDE.Types.Options: instance GHC.Classes.Eq Development.IDE.Types.Options.CheckProject
+ Development.IDE.Types.Options: instance GHC.Classes.Eq Development.IDE.Types.Options.LspConfig
+ Development.IDE.Types.Options: instance GHC.Classes.Eq Development.IDE.Types.Options.OptHaddockParse
+ Development.IDE.Types.Options: instance GHC.Classes.Ord Development.IDE.Types.Options.CheckParents
+ Development.IDE.Types.Options: instance GHC.Classes.Ord Development.IDE.Types.Options.CheckProject
+ Development.IDE.Types.Options: instance GHC.Classes.Ord Development.IDE.Types.Options.LspConfig
+ Development.IDE.Types.Options: instance GHC.Classes.Ord Development.IDE.Types.Options.OptHaddockParse
+ Development.IDE.Types.Options: instance GHC.Enum.Enum Development.IDE.Types.Options.OptHaddockParse
+ Development.IDE.Types.Options: instance GHC.Generics.Generic Development.IDE.Types.Options.CheckParents
+ Development.IDE.Types.Options: instance GHC.Generics.Generic Development.IDE.Types.Options.LspConfig
+ Development.IDE.Types.Options: instance GHC.Show.Show Development.IDE.Types.Options.CheckParents
+ Development.IDE.Types.Options: instance GHC.Show.Show Development.IDE.Types.Options.CheckProject
+ Development.IDE.Types.Options: instance GHC.Show.Show Development.IDE.Types.Options.LspConfig
+ Development.IDE.Types.Options: instance GHC.Show.Show Development.IDE.Types.Options.OptHaddockParse
+ Development.IDE.Types.Options: newtype CheckProject
- Development.IDE.Core.FileStore: setFileModified :: IdeState -> NormalizedFilePath -> IO ()
+ Development.IDE.Core.FileStore: setFileModified :: IdeState -> Bool -> NormalizedFilePath -> IO ()
- Development.IDE.Core.OfInterest: getFilesOfInterest :: Action (HashSet NormalizedFilePath)
+ Development.IDE.Core.OfInterest: getFilesOfInterest :: Action (HashMap NormalizedFilePath FileOfInterestStatus)
- Development.IDE.Core.OfInterest: modifyFilesOfInterest :: IdeState -> (HashSet NormalizedFilePath -> HashSet NormalizedFilePath) -> IO ()
+ Development.IDE.Core.OfInterest: modifyFilesOfInterest :: IdeState -> (HashMap NormalizedFilePath FileOfInterestStatus -> HashMap NormalizedFilePath FileOfInterestStatus) -> IO ()
- Development.IDE.Core.OfInterest: setFilesOfInterest :: IdeState -> HashSet NormalizedFilePath -> IO ()
+ Development.IDE.Core.OfInterest: setFilesOfInterest :: IdeState -> HashMap NormalizedFilePath FileOfInterestStatus -> IO ()
- Development.IDE.Core.PositionMapping: PositionDelta :: !Position -> Maybe Position -> !Position -> Maybe Position -> PositionDelta
+ Development.IDE.Core.PositionMapping: PositionDelta :: !Position -> PositionResult Position -> !Position -> PositionResult Position -> PositionDelta
- Development.IDE.Core.PositionMapping: [fromDelta] :: PositionDelta -> !Position -> Maybe Position
+ Development.IDE.Core.PositionMapping: [fromDelta] :: PositionDelta -> !Position -> PositionResult Position
- Development.IDE.Core.PositionMapping: [toDelta] :: PositionDelta -> !Position -> Maybe Position
+ Development.IDE.Core.PositionMapping: [toDelta] :: PositionDelta -> !Position -> PositionResult Position
- Development.IDE.Core.PositionMapping: fromCurrent :: Range -> Text -> Position -> Maybe Position
+ Development.IDE.Core.PositionMapping: fromCurrent :: Range -> Text -> Position -> PositionResult Position
- Development.IDE.Core.PositionMapping: toCurrent :: Range -> Text -> Position -> Maybe Position
+ Development.IDE.Core.PositionMapping: toCurrent :: Range -> Text -> Position -> PositionResult Position
- Development.IDE.Core.RuleTypes: TcModuleResult :: TypecheckedModule -> HomeModInfo -> TcModuleResult
+ Development.IDE.Core.RuleTypes: TcModuleResult :: TypecheckedModule -> HomeModInfo -> !Bool -> TcModuleResult
- Development.IDE.Core.Shake: ShakeExtras :: (FromServerMessage -> IO ()) -> Debouncer NormalizedUri -> Logger -> Var (HashMap TypeRep Dynamic) -> Var Values -> Var DiagnosticStore -> Var DiagnosticStore -> Var (HashMap NormalizedUri [Diagnostic]) -> Var (HashMap NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping))) -> Var (HashMap NormalizedFilePath Int) -> (ProgressEvent -> IO ()) -> IdeTesting -> MVar ShakeSession -> WithProgressFunc -> WithIndefiniteProgressFunc -> ([DelayedAction ()] -> IO ()) -> IORef NameCache -> ShakeExtras
+ Development.IDE.Core.Shake: ShakeExtras :: (FromServerMessage -> IO ()) -> Debouncer NormalizedUri -> Logger -> Var (HashMap TypeRep Dynamic) -> Var Values -> Var DiagnosticStore -> Var DiagnosticStore -> Var (HashMap NormalizedUri [Diagnostic]) -> Var (HashMap NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping))) -> Var (HashMap NormalizedFilePath Int) -> (ProgressEvent -> IO ()) -> IdeTesting -> MVar ShakeSession -> WithProgressFunc -> WithIndefiniteProgressFunc -> ([DelayedAction ()] -> IO ()) -> IORef NameCache -> Var (Hashed KnownTargets) -> Var ExportsMap -> ActionQueue -> ShakeExtras
- Development.IDE.Core.Shake: shakeEnqueue :: IdeState -> DelayedAction a -> IO (IO a)
+ Development.IDE.Core.Shake: shakeEnqueue :: ShakeExtras -> DelayedAction a -> IO (IO a)
- Development.IDE.Core.Shake: shakeRestart :: IdeState -> [DelayedAction a] -> IO ()
+ Development.IDE.Core.Shake: shakeRestart :: IdeState -> [DelayedAction ()] -> IO ()
- Development.IDE.GHC.Error: srcSpanToFilename :: SrcSpan -> FilePath
+ Development.IDE.GHC.Error: srcSpanToFilename :: SrcSpan -> Maybe FilePath
- Development.IDE.GHC.Error: srcSpanToLocation :: SrcSpan -> Location
+ Development.IDE.GHC.Error: srcSpanToLocation :: SrcSpan -> Maybe Location
- Development.IDE.GHC.Error: srcSpanToRange :: SrcSpan -> Range
+ Development.IDE.GHC.Error: srcSpanToRange :: SrcSpan -> Maybe Range
- Development.IDE.GHC.Util: newHscEnvEq :: HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq
+ Development.IDE.GHC.Util: newHscEnvEq :: FilePath -> HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq
- Development.IDE.Import.DependencyInformation: DependencyInformation :: !FilePathIdMap (NonEmpty NodeError) -> !FilePathIdMap ShowableModuleName -> !FilePathIdMap FilePathIdSet -> !FilePathIdMap (Set InstalledUnitId) -> !PathIdMap -> !BootIdMap -> DependencyInformation
+ Development.IDE.Import.DependencyInformation: DependencyInformation :: !FilePathIdMap (NonEmpty NodeError) -> !FilePathIdMap ShowableModuleName -> !FilePathIdMap FilePathIdSet -> !IntMap IntSet -> !FilePathIdMap (Set InstalledUnitId) -> !PathIdMap -> !BootIdMap -> DependencyInformation
- Development.IDE.LSP.LanguageServer: runLanguageServer :: forall config. Show config => Options -> PartialHandlers config -> (InitializeRequest -> Either Text config) -> (DidChangeConfigurationNotification -> Either Text config) -> (IO LspId -> (FromServerMessage -> IO ()) -> VFSHandle -> ClientCapabilities -> WithProgressFunc -> WithIndefiniteProgressFunc -> IO IdeState) -> IO ()
+ Development.IDE.LSP.LanguageServer: runLanguageServer :: forall config. Show config => Options -> PartialHandlers config -> (InitializeRequest -> Either Text config) -> (DidChangeConfigurationNotification -> Either Text config) -> (IO LspId -> (FromServerMessage -> IO ()) -> VFSHandle -> ClientCapabilities -> WithProgressFunc -> WithIndefiniteProgressFunc -> IO (Maybe config) -> Maybe FilePath -> IO IdeState) -> IO ()
- Development.IDE.Types.Location: readSrcSpan :: ReadS SrcSpan
+ Development.IDE.Types.Location: readSrcSpan :: ReadS RealSrcSpan
- Development.IDE.Types.Options: IdeOptions :: (ParsedSource -> IdePreprocessedSource) -> Action IdeGhcSession -> IdePkgLocationOptions -> [String] -> Int -> Maybe FilePath -> Maybe FilePath -> IdeTesting -> IdeReportProgress -> String -> Bool -> [Text] -> IdeDefer -> IdeOptions
+ Development.IDE.Types.Options: IdeOptions :: (ParsedSource -> IdePreprocessedSource) -> Action IdeGhcSession -> IdePkgLocationOptions -> [String] -> Int -> Maybe FilePath -> Maybe FilePath -> IdeTesting -> IdeReportProgress -> String -> Bool -> [Text] -> IdeDefer -> CheckProject -> CheckParents -> OptHaddockParse -> (DynFlags -> DynFlags) -> IdeOptions

Files

CHANGELOG.md view
@@ -1,4 +1,26 @@-### unreleased+### 0.4.0 (2020-09-15)+* Fixes for GHC source plugins: dotpreprocessor works now - (srid)+* Use implicit-hie when no explicit hie.yaml (#782) - (Javier Neira)+* Extend position mapping with fuzzy ranges (#785) - (wz1000)+* Sort import suggestions (#793) - (Pepe Iborra)+* Save source files with HIE files (#701) - (fendor)+* Fully asynchronous request handling (#767) - (Pepe Iborra)+* Refinement holes (#748) - (Pepe Iborra)+* Fix haddock to markdown conversion (#757) - (George Thomas)+* Expose `getCompletionsLSP` to allow completions in hls (#756) - (wz1000)+* Suggestions for missing imports from local modules (#739) - (Pepe Iborra)+* Dynamically load libm on Linux for each new session (#723) - (Luke Lau)+* Use InitializeParams.rootUri for initial session setup (#713) - (shaurya gupta)+* Show documentation on hover for symbols defined in the same module (#691) - (wz1000)+* Suggest open imports (#740) - (Pepe Iborra)+* module Development.IDE (#724) - (Pepe Iborra)+* Ignore -Werror (#738) - (Pepe Iborra)+* Fix issue #710: fix suggest delete binding  (#728) - (Ray Shih)+* Generate doc file URL via LSP (to fix it for Windows) (#721) - (Nick Dunets)+* Fix `.hie` file location for `.hs-boot` files (#690) - (wz1000)+* Use argsVerbose to determine log level in test mode (#717) - (Ziyang Liu)+* output which cradle files were found (#716) - (Adam Sandberg Eriksson)+* Typecheck entire project on Initial Load and typecheck reverse dependencies of a file on saving (#688) - (wz1000)  ### 0.3.0 (2020-09-02) 
README.md view
@@ -95,6 +95,24 @@  `ghcide` has been designed to handle projects with hundreds or thousands of modules. If `ghci` can handle it, then `ghcide` should be able to handle it. The only caveat is that this currently requires GHC >= 8.6, and that the first time a module is loaded in the editor will trigger generation of support files in the background if those do not already exist. +### Configuration++`ghcide` accepts the following lsp configuration options:++```typescript+{+  // When to check the dependents of a module+  // AlwaysCheck means retypechecking them on every change+  // CheckOnSave means dependent/parent modules will only be checked when you save+  // "CheckOnSaveAndClose" by default+  checkParents : "NeverCheck" | "CheckOnClose" | "CheckOnSaveAndClose" | "AlwaysCheck" | ,+  // Whether to check the entire project on initial load+  // true by default+  checkProject : boolean++}+```+ ### Using with VS Code  You can install the VSCode extension from the [VSCode@@ -311,15 +329,17 @@ See the [tasty-rerun](https://hackage.haskell.org/package/tasty-rerun-1.1.17/docs/Test-Tasty-Ingredients-Rerun.html) documentation for other options.  If you are touching performance sensitive code, take the time to run a differential-benchmark between HEAD and upstream using the benchHist script. The configuration in-`bench/hist.yaml` is setup to do this by default assuming upstream is-`origin/master`. Run the benchmarks with `stack`:+benchmark between HEAD and master using the benchHist script. This assumes that+"master" points to the upstream master. +Run the benchmarks with `stack`:+     export STACK_YAML=...     stack bench -It should take around 15 minutes and the results will be stored in the `bench-hist` folder.-To interpret the results, see the comments in the `bench/hist/Main.hs` module.+It should take around 15 minutes and the results will be stored in the `bench-hist` folder. To interpret the results, see the comments in the `bench/hist/Main.hs` module.++More details in [bench/README](bench/README.md)  ### Building the extension 
bench/hist/Main.hs view
@@ -25,15 +25,14 @@    For diff graphs, the "previous version" is the preceding entry in the list of versions    in the config file. A possible improvement is to obtain this info via `git rev-list`. -   The script relies on stack for building and running all the binaries.-    To execute the script: -   > stack bench+   > cabal/stack bench     To build a specific analysis, enumerate the desired file artifacts     > stack bench --ba "bench-hist/HEAD/results.csv bench-hist/HEAD/edit.diff.svg"+   > cabal bench --benchmark-options "bench-hist/HEAD/results.csv bench-hist/HEAD/edit.diff.svg"   -} {-# LANGUAGE DeriveAnyClass    #-}@@ -42,6 +41,7 @@  import Control.Applicative (Alternative (empty)) import Control.Monad (when, forM, forM_, replicateM)+import Data.Char (toLower) import Data.Foldable (find) import Data.Maybe (fromMaybe) import Data.Text (Text)@@ -103,8 +103,10 @@       readSamples = askOracle $ GetSamples ()       getParent = askOracle . GetParent -  build <- liftIO $ outputFolder <$> readConfigIO config+  configStatic <- liftIO $ readConfigIO config   ghcideBenchPath <- ghcideBench <$> liftIO (readConfigIO config)+  let build = outputFolder configStatic+      buildSystem = buildTool configStatic    phony "all" $ do     Config {..} <- readConfig config@@ -139,11 +141,8 @@     &%> \[out, ghcpath] -> do       liftIO $ createDirectoryIfMissing True $ dropFileName out       need =<< getDirectoryFiles "." ["src//*.hs", "exe//*.hs", "ghcide.cabal"]-      cmd_-          ( "stack --local-bin-path=" <> takeDirectory out-              <> " --stack-yaml=stack88.yaml build ghcide:ghcide --copy-bins --ghc-options -rtsopts"-          )-      Stdout ghcLoc <- cmd (s "stack --stack-yaml=stack88.yaml exec which ghc")+      cmd_ $ buildGhcide buildSystem (takeDirectory out)+      ghcLoc <- findGhc buildSystem       writeFile' ghcpath ghcLoc    [ build -/- "*/ghcide",@@ -155,13 +154,8 @@       commitid <- readFile' $ b </> ver </> "commitid"       cmd_ $ "git worktree add bench-temp " ++ commitid       flip actionFinally (cmd_ (s "git worktree remove bench-temp --force")) $ do-        Stdout ghcLoc <- cmd [Cwd "bench-temp"] (s "stack --stack-yaml=stack88.yaml exec which ghc")-        cmd_-          [Cwd "bench-temp"]-          ( "stack --local-bin-path=../"-              <> takeDirectory out-              <> " --stack-yaml=stack88.yaml build ghcide:ghcide --copy-bins --ghc-options -rtsopts"-          )+        ghcLoc <- findGhc buildSystem+        cmd_ [Cwd "bench-temp"] $ buildGhcide buildSystem (".." </> takeDirectory out)         writeFile' ghcpath ghcLoc    priority 8000 $@@ -198,7 +192,7 @@                 RemEnv "GHC_PACKAGE_PATH",                 AddPath [takeDirectory ghcPath, "."] []               ]-              ghcideBenchPath+              ghcideBenchPath $               [ "--timeout=3000",                 "-v",                 "--samples=" <> show samples,@@ -208,7 +202,8 @@                 "--ghcide=" <> ghcide,                 "--select",                 unescaped (unescapeExperiment (Escaped $ dropExtension exp))-              ]+              ] +++              [ "--stack" | Stack == buildSystem]           cmd_ Shell $ "mv *.benchmark-gcStats " <> dropFileName outcsv    build -/- "results.csv" %> \out -> do@@ -259,8 +254,31 @@         title = show (unescapeExperiment exp) <> " - live bytes over time"     plotDiagram False diagram out -----------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------- +buildGhcide :: BuildSystem -> String -> String+buildGhcide Cabal out = unwords+    ["cabal install"+    ,"exe:ghcide"+    ,"--installdir=" ++ out+    ,"--install-method=copy"+    ,"--overwrite-policy=always"+    ,"--ghc-options -rtsopts"+    ]+buildGhcide Stack out =+    "stack --local-bin-path=" <> out+        <> " build ghcide:ghcide --copy-bins --ghc-options -rtsopts"+++findGhc :: BuildSystem -> Action FilePath+findGhc Cabal =+    liftIO $ fromMaybe (error "ghc is not in the PATH") <$> findExecutable "ghc"+findGhc Stack = do+    Stdout ghcLoc <- cmd (s "stack exec which ghc")+    return ghcLoc++--------------------------------------------------------------------------------+ data Config = Config   { experiments :: [Unescaped String],     samples :: Natural,@@ -268,7 +286,8 @@     -- | Path to the ghcide-bench binary for the experiments     ghcideBench :: FilePath,     -- | Output folder ('foo' works, 'foo/bar' does not)-    outputFolder :: String+    outputFolder :: String,+    buildTool :: BuildSystem   }   deriving (Generic, Show)   deriving anyclass (FromJSON, ToJSON)@@ -312,6 +331,18 @@   | otherwise = findPrev name (y : xx) findPrev name _ = name +data BuildSystem = Cabal | Stack+  deriving (Eq, Read, Show)++instance FromJSON BuildSystem where+    parseJSON x = fromString . map toLower <$> parseJSON x+      where+        fromString "stack" = Stack+        fromString "cabal" = Cabal+        fromString other = error $ "Unknown build system: " <> other++instance ToJSON BuildSystem where+    toJSON = toJSON . show ----------------------------------------------------------------------------------------------------  -- | A line in the output of -S
exe/Main.hs view
@@ -8,6 +8,7 @@ import Arguments import Control.Concurrent.Extra import Control.Monad.Extra+import Control.Lens ( (^.) ) import Data.Default import Data.List.Extra import Data.Maybe@@ -29,10 +30,12 @@ import Development.IDE.Plugin import Development.IDE.Plugin.Completions as Completions import Development.IDE.Plugin.CodeAction as CodeAction+import Development.IDE.Plugin.Test as Test import Development.IDE.Session import qualified Language.Haskell.LSP.Core as LSP import Language.Haskell.LSP.Messages import Language.Haskell.LSP.Types+import Language.Haskell.LSP.Types.Lens (params, initializationOptions) import Development.IDE.LSP.LanguageServer import qualified System.Directory.Extra as IO import System.Environment@@ -43,7 +46,8 @@ import System.Time.Extra import Paths_ghcide import Development.GitRev-import qualified Data.HashSet as HashSet+import qualified Data.HashMap.Strict as HashMap+import qualified Data.Aeson as J  import HIE.Bios.Cradle @@ -78,8 +82,14 @@     command <- makeLspCommandId "typesignature.add"      let plugins = Completions.plugin <> CodeAction.plugin-        onInitialConfiguration = const $ Right ()-        onConfigurationChange  = const $ Right ()+            <> if argsTesting then Test.plugin else mempty+        onInitialConfiguration :: InitializeRequest -> Either T.Text LspConfig+        onInitialConfiguration x = case x ^. params . initializationOptions of+          Nothing -> Right defaultLspConfig+          Just v -> case J.fromJSON v of+            J.Error err -> Left $ T.pack err+            J.Success a -> Right a+        onConfigurationChange = const $ Left "Updating Not supported"         options = def { LSP.executeCommandCommands = Just [command]                       , LSP.completionTriggerCharacters = Just "."                       }@@ -88,15 +98,18 @@         t <- offsetTime         hPutStrLn stderr "Starting LSP server..."         hPutStrLn stderr "If you are seeing this in a terminal, you probably should have run ghcide WITHOUT the --lsp option!"-        runLanguageServer options (pluginHandler plugins) onInitialConfiguration onConfigurationChange $ \getLspId event vfs caps wProg wIndefProg -> do+        runLanguageServer options (pluginHandler plugins) onInitialConfiguration onConfigurationChange $ \getLspId event vfs caps wProg wIndefProg getConfig rootPath -> do             t <- t             hPutStrLn stderr $ "Started LSP server in " ++ showDuration t-            sessionLoader <- loadSession dir+            sessionLoader <- loadSession $ fromMaybe dir rootPath+            config <- fromMaybe defaultLspConfig <$> getConfig             let options = (defaultIdeOptions sessionLoader)                     { optReportProgress = clientSupportsProgress caps                     , optShakeProfiling = argsShakeProfiling                     , optTesting        = IdeTesting argsTesting                     , optThreads        = argsThreads+                    , optCheckParents   = checkParents config+                    , optCheckProject   = checkProject config                     }                 logLevel = if argsVerbose then minBound else Info             debouncer <- newAsyncDebouncer@@ -121,15 +134,17 @@         let ucradles = nubOrd cradles         let n = length ucradles         putStrLn $ "Found " ++ show n ++ " cradle" ++ ['s' | n /= 1]+        when (n > 0) $ putStrLn $ "  (" ++ intercalate ", " (catMaybes ucradles) ++ ")"         putStrLn "\nStep 3/4: Initializing the IDE"         vfs <- makeVFSHandle         debouncer <- newAsyncDebouncer-        let dummyWithProg _ _ f = f (const (pure ()))+        let logLevel = if argsVerbose then minBound else Info+            dummyWithProg _ _ f = f (const (pure ()))         sessionLoader <- loadSession dir-        ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) dummyWithProg (const (const id)) (logger minBound) debouncer (defaultIdeOptions sessionLoader)  vfs+        ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) dummyWithProg (const (const id)) (logger logLevel) debouncer (defaultIdeOptions sessionLoader)  vfs          putStrLn "\nStep 4/4: Type checking the files"-        setFilesOfInterest ide $ HashSet.fromList $ map toNormalizedFilePath' files+        setFilesOfInterest ide $ HashMap.fromList $ map ((, OnDisk) . toNormalizedFilePath') files         results <- runAction "User TypeCheck" ide $ uses TypeCheck (map toNormalizedFilePath' files)         let (worked, failed) = partition fst $ zip (map isJust results) files         when (failed /= []) $@@ -157,4 +172,3 @@ showEvent lock (EventFileDiagnostics (toNormalizedFilePath' -> file) diags) =     withLock lock $ T.putStrLn $ showDiagnosticsColored $ map (file,ShowDiag,) diags showEvent lock e = withLock lock $ print e-
ghcide.cabal view
@@ -2,7 +2,7 @@ build-type:         Simple category:           Development name:               ghcide-version:            0.3.0+version:            0.4.0 license:            Apache-2.0 license-file:       LICENSE author:             Digital Asset and Ghcide contributors@@ -13,7 +13,7 @@     A library for building Haskell IDE's on top of the GHC API. homepage:           https://github.com/haskell/ghcide#readme bug-reports:        https://github.com/haskell/ghcide/issues-tested-with:        GHC==8.6.5+tested-with:        GHC>=8.4.4 extra-source-files: include/ghc-api-version.h README.md CHANGELOG.md                     test/data/hover/*.hs                     test/data/multi/cabal.project@@ -83,10 +83,11 @@         ghc >= 8.4,         -- These dependencies are used by Development.IDE.Session and are         -- Haskell specific. So don't use them when building with -fghc-lib!-        ghc-check,+        ghc-check >=0.5.0.1,         ghc-paths,         cryptohash-sha1 >=0.11.100 && <0.12,-        hie-bios == 0.6.*,+        hie-bios >= 0.7.1 && < 0.8.0,+        implicit-hie-cradle >= 0.2.0.0 && < 0.3,         base16-bytestring >=0.1.1 && <0.2     if os(windows)       build-depends:@@ -101,6 +102,7 @@         BangPatterns         DeriveFunctor         DeriveGeneric+        FlexibleContexts         GeneralizedNewtypeDeriving         LambdaCase         NamedFieldPuns@@ -117,6 +119,7 @@     include-dirs:         include     exposed-modules:+        Development.IDE         Development.IDE.Compat         Development.IDE.Core.Debouncer         Development.IDE.Core.FileStore@@ -138,12 +141,14 @@         Development.IDE.LSP.Server         Development.IDE.Spans.Common         Development.IDE.Types.Diagnostics+        Development.IDE.Types.Exports         Development.IDE.Types.Location         Development.IDE.Types.Logger         Development.IDE.Types.Options         Development.IDE.Plugin         Development.IDE.Plugin.Completions         Development.IDE.Plugin.CodeAction+        Development.IDE.Plugin.Test      -- Unfortunately, we cannot use loadSession with ghc-lib since hie-bios uses     -- the real GHC library and the types are incompatible. Furthermore, when@@ -177,6 +182,7 @@         Development.IDE.Plugin.CodeAction.RuleTypes         Development.IDE.Plugin.Completions.Logic         Development.IDE.Plugin.Completions.Types+        Development.IDE.Types.Action     if (impl(ghc > 8.5) && impl(ghc < 8.7)) && !flag(ghc-lib)       hs-source-dirs: src-ghc86       other-modules:@@ -195,11 +201,7 @@       other-modules:         Development.IDE.GHC.HieAst         Development.IDE.GHC.HieBin-    ghc-options: -Wall -Wno-name-shadowing-    -- This is needed to prevent a GHC crash when building-    -- Development.IDE.Session with stack on 8.10.1 on Windows-    if (impl(ghc > 8.9) && os(windows))-      ghc-options: -fexternal-interpreter+    ghc-options: -Wall -Wno-name-shadowing -Wincomplete-uni-patterns  executable ghcide-test-preprocessor     default-language: Haskell2010@@ -221,6 +223,7 @@         BangPatterns         DeriveFunctor         DeriveGeneric+        FlexibleContexts         GeneralizedNewtypeDeriving         LambdaCase         NamedFieldPuns@@ -254,6 +257,7 @@     ghc-options:                 -threaded                 -Wall+                -Wincomplete-uni-patterns                 -Wno-name-shadowing                 -- allow user RTS overrides                 -rtsopts@@ -263,6 +267,7 @@                 "-with-rtsopts=-I0 -qg -A128M"     main-is: Main.hs     build-depends:+        aeson,         base == 4.*,         data-default,         directory,@@ -272,8 +277,9 @@         hashable,         haskell-lsp,         haskell-lsp-types,-        hie-bios >= 0.6.0 && < 0.7,+        hie-bios,         ghcide,+        lens,         optparse-applicative,         text,         unordered-containers@@ -285,6 +291,7 @@         BangPatterns         DeriveFunctor         DeriveGeneric+        FlexibleContexts         GeneralizedNewtypeDeriving         LambdaCase         NamedFieldPuns@@ -328,7 +335,7 @@         haskell-lsp-types,         network-uri,         lens,-        lsp-test >= 0.11.0.1 && < 0.12,+        lsp-test >= 0.11.0.5 && < 0.12,         optparse-applicative,         process,         QuickCheck,@@ -343,9 +350,13 @@         tasty-quickcheck,         tasty-rerun,         text+    if (impl(ghc >= 8.6))+      build-depends:+          record-dot-preprocessor,+          record-hasfield     hs-source-dirs: test/cabal test/exe test/src bench/lib     include-dirs: include-    ghc-options: -threaded -Wall -Wno-name-shadowing+    ghc-options: -threaded -Wall -Wno-name-shadowing -O0     main-is: Main.hs     other-modules:         Development.IDE.Test@@ -355,6 +366,7 @@         BangPatterns         DeriveFunctor         DeriveGeneric+        FlexibleContexts         GeneralizedNewtypeDeriving         LambdaCase         NamedFieldPuns@@ -393,6 +405,7 @@         BangPatterns         DeriveFunctor         DeriveGeneric+        FlexibleContexts         GeneralizedNewtypeDeriving         LambdaCase         NamedFieldPuns
session-loader/Development/IDE/Session.hs view
@@ -25,6 +25,7 @@ import qualified Data.ByteString.Base16 as B16 import Data.Either.Extra import Data.Function+import Data.Hashable import Data.List import Data.IORef import Data.Maybe@@ -32,9 +33,12 @@ import Data.Version import Development.IDE.Core.OfInterest import Development.IDE.Core.Shake+import Development.IDE.Core.RuleTypes+import Development.IDE.GHC.Compat import Development.IDE.GHC.Util import Development.IDE.Session.VersionCheck import Development.IDE.Types.Diagnostics+import Development.IDE.Types.Exports import Development.IDE.Types.Location import Development.IDE.Types.Logger import Development.IDE.Types.Options@@ -43,21 +47,25 @@ import HIE.Bios import HIE.Bios.Environment hiding (getCacheDir) import HIE.Bios.Types+import Hie.Implicit.Cradle (loadImplicitHieCradle) import Language.Haskell.LSP.Core import Language.Haskell.LSP.Messages import Language.Haskell.LSP.Types import System.Directory+import qualified System.Directory.Extra as IO import System.FilePath import System.Info import System.IO -import GHC+import GHCi import DynFlags import HscTypes import Linker import Module import NameCache import Packages+import Control.Exception (evaluate)+import Data.Char  -- | Given a root directory, return a Shake 'Action' which setups an -- 'IdeGhcSession' given a file.@@ -96,9 +104,29 @@   runningCradle <- newVar dummyAs :: IO (Var (Async (IdeResult HscEnvEq,[FilePath])))    return $ do-    ShakeExtras{logger, eventer, restartShakeSession, withIndefiniteProgress, ideNc} <- getShakeExtras-    IdeOptions{optTesting = IdeTesting optTesting} <- getIdeOptions+    extras@ShakeExtras{logger, eventer, restartShakeSession,+                       withIndefiniteProgress, ideNc, knownTargetsVar+                      } <- getShakeExtras +    IdeOptions{ optTesting = IdeTesting optTesting+              , optCheckProject = CheckProject checkProject+              , optCustomDynFlags+              } <- getIdeOptions++        -- populate the knownTargetsVar with all the+        -- files in the project so that `knownFiles` can learn about them and+        -- we can generate a complete module graph+    let extendKnownTargets newTargets = do+          knownTargets <- forM newTargets $ \TargetDetails{..} -> do+            found <- filterM (IO.doesFileExist . fromNormalizedFilePath) targetLocations+            return (targetModule, found)+          modifyVar_ knownTargetsVar $ traverseHashed $ \known -> do+            let known' = HM.unionWith (<>) known $ HM.fromList knownTargets+            when (known /= known') $+                logDebug logger $ "Known files updated: " <>+                    T.pack(show $ (HM.map . map) fromNormalizedFilePath known')+            evaluate known'+     -- Create a new HscEnv from a hieYaml root and a set of options     -- If the hieYaml file already has an HscEnv, the new component is     -- combined with the components in the old HscEnv into a new HscEnv@@ -109,7 +137,7 @@           -- Parse DynFlags for the newly discovered component           hscEnv <- emptyHscEnv ideNc libDir           (df, targets) <- evalGhcEnv hscEnv $-              setOptions opts (hsc_dflags hscEnv)+              first optCustomDynFlags <$> setOptions opts (hsc_dflags hscEnv)           let deps = componentDependencies opts ++ maybeToList hieYaml           dep_info <- getDependencyInfo deps           -- Now lookup to see whether we are combining with an existing HscEnv@@ -170,9 +198,26 @@       let session :: (Maybe FilePath, NormalizedFilePath, ComponentOptions, FilePath)-                -> IO (IdeResult HscEnvEq,[FilePath])+                -> IO ([NormalizedFilePath],(IdeResult HscEnvEq,[FilePath]))         session args@(hieYaml, _cfp, _opts, _libDir) = do           (hscEnv, new, old_deps) <- packageSetup args++          -- Whenever we spin up a session on Linux, dynamically load libm.so.6+          -- in. We need this in case the binary is statically linked, in which+          -- case the interactive session will fail when trying to load+          -- ghc-prim, which happens whenever Template Haskell is being+          -- evaluated or haskell-language-server's eval plugin tries to run+          -- some code. If the binary is dynamically linked, then this will have+          -- no effect.+          -- See https://github.com/haskell/haskell-language-server/issues/221+          when (os == "linux") $ do+            initObjLinker hscEnv+            res <- loadDLL hscEnv "libm.so.6"+            case res of+              Nothing -> pure ()+              Just err -> hPutStrLn stderr $+                "Error dynamically loading libm.so.6:\n" <> err+           -- Make a map from unit-id to DynFlags, this is used when trying to           -- resolve imports. (especially PackageImports)           let uids = map (\ci -> (componentUnitId ci, componentDynFlags ci)) (new : old_deps)@@ -182,30 +227,41 @@            -- New HscEnv for the component in question, returns the new HscEnvEq and           -- a mapping from FilePath to the newly created HscEnvEq.-          let new_cache = newComponentCache logger hscEnv uids+          let new_cache = newComponentCache logger hieYaml hscEnv uids           (cs, res) <- new_cache new           -- Modified cache targets for everything else in the hie.yaml file           -- which now uses the same EPS and so on           cached_targets <- concatMapM (fmap fst . new_cache) old_deps++          let all_targets = cs ++ cached_targets+           modifyVar_ fileToFlags $ \var -> do-              pure $ Map.insert hieYaml (HM.fromList (cs ++ cached_targets)) var+              pure $ Map.insert hieYaml (HM.fromList (concatMap toFlagsMap all_targets)) var +          extendKnownTargets all_targets+           -- Invalidate all the existing GhcSession build nodes by restarting the Shake session           invalidateShakeCache           restartShakeSession [kick] -          return (second Map.keys res)+          let resultCachedTargets = concatMap targetLocations all_targets -    let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, [FilePath])+          return (resultCachedTargets, second Map.keys res)++    let consultCradle :: Maybe FilePath -> FilePath -> IO ([NormalizedFilePath], (IdeResult HscEnvEq, [FilePath]))         consultCradle hieYaml cfp = do-           when optTesting $ eventer $ notifyCradleLoaded cfp-           logInfo logger $ T.pack ("Consulting the cradle for " <> show cfp)+           lfp <- flip makeRelative cfp <$> getCurrentDirectory+           logInfo logger $ T.pack ("Consulting the cradle for " <> show lfp) -           cradle <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle hieYaml-           -- Display a user friendly progress message here: They probably don't know what a-           -- cradle is-           let progMsg = "Setting up project " <> T.pack (takeBaseName (cradleRootDir cradle))+           when (isNothing hieYaml) $ eventer $ notifyUserImplicitCradle lfp +           cradle <- maybe (loadImplicitHieCradle $ addTrailingPathSeparator dir) loadCradle hieYaml++           when optTesting $ eventer $ notifyCradleLoaded lfp++           -- Display a user friendly progress message here: They probably don't know what a cradle is+           let progMsg = "Setting up " <> T.pack (takeBaseName (cradleRootDir cradle))+                         <> " (for " <> T.pack lfp <> ")"            eopts <- withIndefiniteProgress progMsg NotCancellable $              cradleToOptsAndLibDir cradle cfp @@ -219,7 +275,7 @@                  InstallationNotFound{..} ->                      error $ "GHC installation not found in libdir: " <> libdir                  InstallationMismatch{..} ->-                     return (([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),[])+                     return ([],(([renderPackageSetupException cfp GhcVersionMismatch{..}], Nothing),[]))                  InstallationChecked _compileTime _ghcLibCheck ->                    session (hieYaml, toNormalizedFilePath' cfp, opts, libDir)              -- Failure case, either a cradle error or the none cradle@@ -229,11 +285,12 @@                let res = (map (renderCradleError ncfp) err, Nothing)                modifyVar_ fileToFlags $ \var -> do                  pure $ Map.insertWith HM.union hieYaml (HM.singleton ncfp (res, dep_info)) var-               return (res,[])+               return ([ncfp],(res,[]))      -- This caches the mapping from hie.yaml + Mod.hs -> [String]     -- Returns the Ghc session and the cradle dependencies-    let sessionOpts :: (Maybe FilePath, FilePath) -> IO (IdeResult HscEnvEq, [FilePath])+    let sessionOpts :: (Maybe FilePath, FilePath)+                    -> IO ([NormalizedFilePath], (IdeResult HscEnvEq, [FilePath]))         sessionOpts (hieYaml, file) = do           v <- fromMaybe HM.empty . Map.lookup hieYaml <$> readVar fileToFlags           cfp <- canonicalizePath file@@ -248,29 +305,43 @@                   -- Keep the same name cache                   modifyVar_ hscEnvs (return . Map.adjust (\(h, _) -> (h, [])) hieYaml )                   consultCradle hieYaml cfp-                else return (opts, Map.keys old_di)+                else return (HM.keys v, (opts, Map.keys old_di))             Nothing -> consultCradle hieYaml cfp      -- The main function which gets options for a file. We only want one of these running     -- at a time. Therefore the IORef contains the currently running cradle, if we try     -- to get some more options then we wait for the currently running action to finish     -- before attempting to do so.-    let getOptions :: FilePath -> IO (IdeResult HscEnvEq, [FilePath])+    let getOptions :: FilePath -> IO ([NormalizedFilePath],(IdeResult HscEnvEq, [FilePath]))         getOptions file = do             hieYaml <- cradleLoc file             sessionOpts (hieYaml, file) `catch` \e ->-                return (([renderPackageSetupException file e], Nothing),[])+                return ([],(([renderPackageSetupException file e], Nothing),[]))      returnWithVersion $ \file -> do-      liftIO $ join $ mask_ $ modifyVar runningCradle $ \as -> do+      (cs, opts) <- liftIO $ join $ mask_ $ modifyVar runningCradle $ \as -> do         -- If the cradle is not finished, then wait for it to finish.         void $ wait as         as <- async $ getOptions file-        return (as, wait as)+        return (fmap snd as, wait as)+      unless (null cs) $ do+        cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) cs+        -- Typecheck all files in the project on startup+        void $ shakeEnqueue extras $ mkDelayedAction "InitialLoad" Debug $ void $ do+          when checkProject $ do+            mmt <- uses GetModificationTime cfps'+            let cs_exist = catMaybes (zipWith (<$) cfps' mmt)+            modIfaces <- uses GetModIface cs_exist+            -- update xports map+            extras <- getShakeExtras+            let !exportsMap' = createExportsMap $ mapMaybe (fmap hirModIface) modIfaces+            liftIO $ modifyVar_ (exportsMap extras) $ evaluate . (exportsMap' <>)+      pure opts  -- | Run the specific cradle on a specific FilePath via hie-bios. -- This then builds dependencies or whatever based on the cradle, gets the -- GHC options/dynflags needed for the session and the GHC library directory+ cradleToOptsAndLibDir :: Show a => Cradle a -> FilePath                       -> IO (Either [CradleError] (ComponentOptions, FilePath)) cradleToOptsAndLibDir cradle file = do@@ -300,50 +371,79 @@     initDynLinker env     pure $ setNameCache nc env --- | Convert a target to a list of potential absolute paths.--- A TargetModule can be anywhere listed by the supplied include--- directories--- A target file is a relative path but with a specific prefix so just need--- to canonicalise it.-targetToFile :: [FilePath] -> TargetId -> IO [NormalizedFilePath]-targetToFile is (TargetModule mod) = do+data TargetDetails = TargetDetails+  {+      targetModule :: !ModuleName,+      targetEnv :: !(IdeResult HscEnvEq),+      targetDepends :: !DependencyInfo,+      targetLocations :: ![NormalizedFilePath]+  }++fromTargetId :: [FilePath]          -- ^ import paths+             -> TargetId+             -> IdeResult HscEnvEq+             -> DependencyInfo+             -> IO [TargetDetails]+-- For a target module we consider all the import paths+fromTargetId is (TargetModule mod) env dep = do     let fps = [i </> moduleNameSlashes mod -<.> ext | ext <- exts, i <- is ]         exts = ["hs", "hs-boot", "lhs"]-    mapM (fmap toNormalizedFilePath' . canonicalizePath) fps-targetToFile _ (TargetFile f _) = do-  f' <- canonicalizePath f-  return [toNormalizedFilePath' f']+    locs <- mapM (fmap toNormalizedFilePath' . canonicalizePath) fps+    return [TargetDetails mod env dep locs]+-- For a 'TargetFile' we consider all the possible module names+fromTargetId _ (TargetFile f _) env deps = do+    nf <- toNormalizedFilePath' <$> canonicalizePath f+    return [TargetDetails m env deps [nf] | m <- moduleNames f] +-- >>> moduleNames "src/A/B.hs"+-- [A.B,B]+moduleNames :: FilePath -> [ModuleName]+moduleNames f = map (mkModuleName .intercalate ".") $ init $ tails nameSegments+    where+        nameSegments = reverse+                     $ takeWhile (isUpper . head)+                     $ reverse+                     $ splitDirectories+                     $ dropExtension f++toFlagsMap :: TargetDetails -> [(NormalizedFilePath, (IdeResult HscEnvEq, DependencyInfo))]+toFlagsMap TargetDetails{..} =+    [ (l, (targetEnv, targetDepends)) | l <-  targetLocations]++ setNameCache :: IORef NameCache -> HscEnv -> HscEnv setNameCache nc hsc = hsc { hsc_NC = nc } - -- | Create a mapping from FilePaths to HscEnvEqs newComponentCache          :: Logger+         -> Maybe FilePath -- Path to cradle          -> HscEnv          -> [(InstalledUnitId, DynFlags)]          -> ComponentInfo-         -> IO ([(NormalizedFilePath, (IdeResult HscEnvEq, DependencyInfo))], (IdeResult HscEnvEq, DependencyInfo))-newComponentCache logger hsc_env uids ci = do+         -> IO ( [TargetDetails], (IdeResult HscEnvEq, DependencyInfo))+newComponentCache logger cradlePath hsc_env uids ci = do     let df = componentDynFlags ci     let hscEnv' = hsc_env { hsc_dflags = df                           , hsc_IC = (hsc_IC hsc_env) { ic_dflags = df } } -    henv <- newHscEnvEq hscEnv' uids-    let res = (([], Just henv), componentDependencyInfo ci)+    let newFunc = maybe newHscEnvEqPreserveImportPaths newHscEnvEq cradlePath+    henv <- newFunc hscEnv' uids+    let targetEnv = ([], Just henv)+        targetDepends = componentDependencyInfo ci+        res = (targetEnv, targetDepends)     logDebug logger ("New Component Cache HscEnvEq: " <> T.pack (show res)) -    let is = importPaths df-    ctargets <- concatMapM (targetToFile is  . targetId) (componentTargets ci)+    let mk t = fromTargetId (importPaths df) (targetId t) targetEnv targetDepends+    ctargets <- concatMapM mk (componentTargets ci)+     -- A special target for the file which caused this wonderful     -- component to be created. In case the cradle doesn't list all the targets for     -- the component, in which case things will be horribly broken anyway.     -- Otherwise, we will immediately attempt to reload this module which     -- causes an infinite loop and high CPU usage.-    let special_target = (componentFP ci, res)-    let xs = map (,res) ctargets-    return (special_target:xs, res)+    let special_target = TargetDetails (mkModuleName "special") targetEnv targetDepends [componentFP ci]+    return (special_target:ctargets, res)  {- Note [Avoiding bad interface files] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~@@ -523,6 +623,7 @@ setOptions (ComponentOptions theOpts compRoot _) dflags = do     (dflags', targets) <- addCmdOpts theOpts dflags     let dflags'' =+          disableWarningsAsErrors $           -- disabled, generated directly by ghcide instead           flip gopt_unset Opt_WriteInterface $           -- disabled, generated directly by ghcide instead@@ -531,6 +632,7 @@           setIgnoreInterfacePragmas $           setLinkerOptions $           disableOptimisation $+          setUpTypedHoles $           makeDynFlagsAbsolute compRoot dflags'     -- initPackages parses the -package flags and     -- sets up the visibility for each component.@@ -571,6 +673,14 @@ -- | Sub directory for the cache path cacheDir :: String cacheDir = "ghcide"++notifyUserImplicitCradle:: FilePath -> FromServerMessage+notifyUserImplicitCradle fp =+    NotShowMessage $+    NotificationMessage "2.0" WindowShowMessage $ ShowMessageParams MtWarning $+      "No [cradle](https://github.com/mpickering/hie-bios#hie-bios) found for "+      <> T.pack fp <>+      ".\n Proceeding with [implicit cradle](https://hackage.haskell.org/package/implicit-hie)"  notifyCradleLoaded :: FilePath -> FromServerMessage notifyCradleLoaded fp =
src-ghc810/Development/IDE/GHC/HieAst.hs view
@@ -1,4 +1,4 @@-+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}  {- Forked from GHC v8.10.1 to work around the readFile side effect in mkHiefile
src-ghc86/Development/IDE/GHC/HieAst.hs view
@@ -1,4 +1,4 @@-+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} {- Forked from GHC v8.8.1 to work around the readFile side effect in mkHiefile 
src-ghc88/Development/IDE/GHC/HieAst.hs view
@@ -1,4 +1,4 @@-+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} {- Forked from GHC v8.8.1 to work around the readFile side effect in mkHiefile 
+ src/Development/IDE.hs view
@@ -0,0 +1,46 @@+module Development.IDE+(+    -- TODO It would be much nicer to enumerate all the exports+    -- and organize them in sections+    module X++) where++import Development.IDE.Core.RuleTypes as X+import Development.IDE.Core.Rules as X+  (GhcSessionIO(..)+  ,getAtPoint+  ,getDefinition+  ,getParsedModule+  ,getTypeDefinition+  )+import Development.IDE.Core.FileExists as X+  (getFileExists)+import Development.IDE.Core.FileStore as X+  (getFileContents)+import Development.IDE.Core.IdeConfiguration as X+  (IdeConfiguration(..)+  ,isWorkspaceFile)+import Development.IDE.Core.OfInterest as X (getFilesOfInterest)+import Development.IDE.Core.Service as X (runAction)+import Development.IDE.Core.Shake as X+  ( IdeState,+    shakeExtras,+    ShakeExtras,+    IdeRule,+    define, defineEarlyCutoff,+    GetModificationTime(GetModificationTime),+    use, useNoFile, uses, useWithStale, useWithStaleFast, useWithStaleFast',+    FastResult(..),+    use_, useNoFile_, uses_, useWithStale_,+    ideLogger,+    actionLogger,+    IdeAction(..), runIdeAction+  )+import Development.IDE.GHC.Error as X+import Development.IDE.GHC.Util as X+import Development.IDE.Plugin as X+import Development.IDE.Types.Diagnostics as X+import Development.IDE.Types.Location as X+import Development.IDE.Types.Logger as X+import Development.Shake as X (Action, action, Rules, RuleResult)
src/Development/IDE/Core/Compile.hs view
@@ -19,7 +19,7 @@   , mkTcModuleResult   , generateByteCode   , generateAndWriteHieFile-  , generateAndWriteHiFile+  , writeHiFile   , getModSummaryFromImports   , loadHieFile   , loadInterface@@ -43,7 +43,6 @@ import Development.IDE.Types.Location  #if MIN_GHC_API_VERSION(8,6,0)-import DynamicLoading (initializePlugins) import LoadIface (loadModuleInterface) #endif @@ -73,6 +72,7 @@ import Control.Monad.Except import Control.Monad.Trans.Except import Data.Bifunctor                           (first, second)+import qualified Data.ByteString as BS import qualified Data.Text as T import           Data.IORef import           Data.List.Extra@@ -100,8 +100,8 @@ parseModule IdeOptions{..} env comp_pkgs filename modTime mbContents =     fmap (either (, Nothing) id) $     evalGhcEnv env $ runExceptT $ do-        (contents, dflags) <- preprocessor filename mbContents-        (diag, modu) <- parseFileContents optPreprocessor dflags comp_pkgs filename modTime contents+        (contents, dflags) <- preprocessor env filename mbContents+        (diag, modu) <- parseFileContents env optPreprocessor dflags comp_pkgs filename modTime contents         return (diag, Just (contents, modu))  @@ -133,9 +133,10 @@         (warnings, tcm) <- withWarnings "typecheck" $ \tweak ->             GHC.typecheckModule $ enableTopLevelWarnings                                 $ demoteIfDefer pm{pm_mod_summary = tweak modSummary'}-        tcm2 <- mkTcModuleResult tcm         let errorPipeline = unDefer . hideDiag dflags-        return (map errorPipeline warnings, tcm2)+            diags = map errorPipeline warnings+        tcm2 <- mkTcModuleResult tcm (any fst diags)+        return (map snd diags, tcm2)     where         demoteIfDefer = if defer then demoteTypeErrorsToWarnings else id @@ -233,11 +234,11 @@ 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-unDefer (Reason Opt_WarnDeferredOutOfScopeVariables, fd) = upgradeWarningToError fd-unDefer ( _                                        , fd) = fd+unDefer :: (WarnReason, FileDiagnostic) -> (Bool, FileDiagnostic)+unDefer (Reason Opt_WarnDeferredTypeErrors         , fd) = (True, upgradeWarningToError fd)+unDefer (Reason Opt_WarnTypedHoles                 , fd) = (True, upgradeWarningToError fd)+unDefer (Reason Opt_WarnDeferredOutOfScopeVariables, fd) = (True, upgradeWarningToError fd)+unDefer ( _                                        , fd) = (False, fd)  upgradeWarningToError :: FileDiagnostic -> FileDiagnostic upgradeWarningToError (nfp, sh, fd) =@@ -257,8 +258,9 @@ mkTcModuleResult     :: GhcMonad m     => TypecheckedModule+    -> Bool     -> m TcModuleResult-mkTcModuleResult tcm = do+mkTcModuleResult tcm upgradedError = do     session <- getSession     let sf = modInfoSafe (tm_checked_module_info tcm) #if MIN_GHC_API_VERSION(8,10,0)@@ -267,7 +269,7 @@     (iface, _) <- liftIO $ mkIfaceTc session Nothing sf details tcGblEnv #endif     let mod_info = HomeModInfo iface details Nothing-    return $ TcModuleResult tcm mod_info+    return $ TcModuleResult tcm mod_info upgradedError   where     (tcGblEnv, details) = tm_internals_ tcm @@ -278,13 +280,13 @@   (tempFilePath, cleanUp) <- newTempFileWithin dir   (write tempFilePath >> renameFile tempFilePath targetPath) `onException` cleanUp -generateAndWriteHieFile :: HscEnv -> TypecheckedModule -> IO [FileDiagnostic]-generateAndWriteHieFile hscEnv tcm =+generateAndWriteHieFile :: HscEnv -> TypecheckedModule -> BS.ByteString -> IO [FileDiagnostic]+generateAndWriteHieFile hscEnv tcm source =   handleGenerationErrors dflags "extended interface generation" $ do     case tm_renamed_source tcm of       Just rnsrc -> do         hf <- runHsc hscEnv $-          GHC.mkHieFile mod_summary (fst $ tm_internals_ tcm) rnsrc ""+          GHC.mkHieFile mod_summary (fst $ tm_internals_ tcm) rnsrc source         atomicFileWrite targetPath $ flip GHC.writeHieFile hf       _ ->         return ()@@ -294,18 +296,14 @@     mod_location = ms_location mod_summary     targetPath   = Compat.ml_hie_file mod_location -generateAndWriteHiFile :: HscEnv -> TcModuleResult -> IO [FileDiagnostic]-generateAndWriteHiFile hscEnv tc =+writeHiFile :: HscEnv -> TcModuleResult -> IO [FileDiagnostic]+writeHiFile hscEnv tc =   handleGenerationErrors dflags "interface generation" $ do     atomicFileWrite targetPath $ \fp ->       writeIfaceFile dflags fp modIface   where     modIface = hm_iface $ tmrModInfo tc-    modSummary = tmrModSummary tc-    targetPath = withBootSuffix $ ml_hi_file $ ms_location $ tmrModSummary tc-    withBootSuffix = case ms_hsc_src modSummary of-                HsBootFile -> addBootSuffix-                _ -> id+    targetPath = ml_hi_file $ ms_location $ tmrModSummary tc     dflags = hsc_dflags hscEnv  handleGenerationErrors :: DynFlags -> T.Text -> IO () -> IO [FileDiagnostic]@@ -407,6 +405,10 @@     , GHC.moduleNameString (GHC.unLoc $ ideclName i) /= "GHC.Prim"     ]) +withBootSuffix :: HscSource -> ModLocation -> ModLocation+withBootSuffix HsBootFile = addBootSuffixLocnOut+withBootSuffix _ = id+ -- | Produce a module summary from a StringBuffer. getModSummaryFromBuffer     :: GhcMonad m@@ -423,7 +425,7 @@   let InstalledUnitId unitId = thisInstalledUnitId dflags   return $ ModSummary     { ms_mod          = mkModule (fsToUnitId unitId) modName-    , ms_location     = modLoc+    , ms_location     = withBootSuffix sourceType modLoc     , ms_hs_date      = modTime     , ms_textual_imps = [imp | (False, imp) <- imports]     , ms_hspp_file    = fp@@ -453,12 +455,13 @@ --   Runs preprocessors as needed. getModSummaryFromImports   :: (HasDynFlags m, ExceptionMonad m, MonadIO m)-  => FilePath+  => HscEnv+  -> FilePath   -> UTCTime   -> Maybe SB.StringBuffer   -> ExceptT [FileDiagnostic] m ModSummary-getModSummaryFromImports fp modTime contents = do-    (contents, dflags) <- preprocessor fp contents+getModSummaryFromImports env fp modTime contents = do+    (contents, dflags) <- preprocessor env fp contents     (srcImports, textualImports, L _ moduleName) <-         ExceptT $ liftIO $ first (diagFromErrMsgs "parser" dflags) <$> GHC.getHeaderImports dflags contents fp fp @@ -483,7 +486,7 @@                 , ms_hspp_file    = fp                 , ms_hspp_opts    = dflags                 , ms_iface_date   = Nothing-                , ms_location     = modLoc+                , ms_location     = withBootSuffix sourceType modLoc                 , ms_obj_date     = Nothing                 , ms_parsed_mod   = Nothing                 , ms_srcimps      = srcImports@@ -529,14 +532,15 @@ -- parsed module (or errors) and any parse warnings. Does not run any preprocessors parseFileContents        :: GhcMonad m-       => (GHC.ParsedSource -> IdePreprocessedSource)+       => HscEnv+       -> (GHC.ParsedSource -> IdePreprocessedSource)        -> DynFlags -- ^ flags to use        -> [PackageName] -- ^ The package imports to ignore        -> FilePath  -- ^ the filename (for source locations)        -> UTCTime   -- ^ the modification timestamp        -> SB.StringBuffer -- ^ Haskell module source text (full Unicode is supported)        -> ExceptT [FileDiagnostic] m ([FileDiagnostic], ParsedModule)-parseFileContents customPreprocessor dflags comp_pkgs filename modTime contents = do+parseFileContents env customPreprocessor dflags comp_pkgs filename modTime contents = do    let loc  = mkRealSrcLoc (mkFastString filename) 1 1    case unP Parser.parseModule (mkPState dflags contents loc) of #if MIN_GHC_API_VERSION(8,10,0)@@ -571,16 +575,16 @@                let parsed' = removePackageImports comp_pkgs parsed                let preproc_warnings = diagFromStrings "parser" DsWarning preproc_warns                ms <- getModSummaryFromBuffer filename modTime dflags parsed' contents+               parsed'' <- liftIO $ applyPluginsParsedResultAction env dflags ms hpm_annotations parsed                let pm =                      ParsedModule {                          pm_mod_summary = ms-                       , pm_parsed_source = parsed'+                       , pm_parsed_source = parsed''                        , pm_extra_src_files=[] -- src imports not allowed                        , pm_annotations = hpm_annotations                       }                    warnings = diagFromErrMsgs "parser" dflags warns                pure (warnings ++ preproc_warnings, pm)-  -- | After parsing the module remove all package imports referring to -- these packages as we have already dealt with what they map to.
src/Development/IDE/Core/FileStore.hs view
@@ -6,20 +6,22 @@ module Development.IDE.Core.FileStore(     getFileContents,     getVirtualFile,-    setBufferModified,     setFileModified,     setSomethingModified,     fileStoreRules,     modificationTime,+    typecheckParents,     VFSHandle,     makeVFSHandle,-    makeLSPVFSHandle+    makeLSPVFSHandle,+    isFileOfInterestRule     ) where  import Development.IDE.GHC.Orphans() import           Development.IDE.Core.Shake import Control.Concurrent.Extra import qualified Data.Map.Strict as Map+import qualified Data.HashMap.Strict as HM import Data.Maybe import qualified Data.Text as T import           Control.Monad.Extra@@ -34,9 +36,11 @@ import qualified Data.ByteString.Char8 as BS import Development.IDE.Types.Diagnostics import Development.IDE.Types.Location-import Development.IDE.Core.OfInterest (kick)+import Development.IDE.Core.OfInterest (getFilesOfInterest, kick) import Development.IDE.Core.RuleTypes+import Development.IDE.Types.Options import qualified Data.Rope.UTF16 as Rope+import Development.IDE.Import.DependencyInformation  #ifdef mingw32_HOST_OS import qualified System.Directory as Dir@@ -90,6 +94,12 @@    }  +isFileOfInterestRule :: Rules ()+isFileOfInterestRule = defineEarlyCutoff $ \IsFileOfInterest f -> do+    filesOfInterest <- getFilesOfInterest+    let res = maybe NotFOI IsFOI $ f `HM.lookup` filesOfInterest+    return (Just $ BS.pack $ show $ hash res, ([], Just res))+ -- | Get the contents of a file, either dirty (if the buffer is modified) or Nothing to mean use from disk. type instance RuleResult GetFileContents = (FileVersion, Maybe T.Text) @@ -117,31 +127,31 @@                 if isDoesNotExistError e && not missingFileDiags                     then return (Nothing, ([], Nothing))                     else return (Nothing, ([diag], Nothing))-  where-    -- Dir.getModificationTime is surprisingly slow since it performs-    -- a ton of conversions. Since we do not actually care about-    -- the format of the time, we can get away with something cheaper.-    -- For now, we only try to do this on Unix systems where it seems to get the-    -- time spent checking file modifications (which happens on every change)-    -- from > 0.5s to ~0.15s.-    -- We might also want to try speeding this up on Windows at some point.-    -- TODO leverage DidChangeWatchedFile lsp notifications on clients that-    -- support them, as done for GetFileExists-    getModTime :: FilePath -> IO (Int64, Int64)-    getModTime f =++-- Dir.getModificationTime is surprisingly slow since it performs+-- a ton of conversions. Since we do not actually care about+-- the format of the time, we can get away with something cheaper.+-- For now, we only try to do this on Unix systems where it seems to get the+-- time spent checking file modifications (which happens on every change)+-- from > 0.5s to ~0.15s.+-- We might also want to try speeding this up on Windows at some point.+-- TODO leverage DidChangeWatchedFile lsp notifications on clients that+-- support them, as done for GetFileExists+getModTime :: FilePath -> IO (Int64, Int64)+getModTime f = #ifdef mingw32_HOST_OS-        do time <- Dir.getModificationTime f-           let !day = fromInteger $ toModifiedJulianDay $ utctDay time-               !dayTime = fromInteger $ diffTimeToPicoseconds $ utctDayTime time-           pure (day, dayTime)+    do time <- Dir.getModificationTime f+       let !day = fromInteger $ toModifiedJulianDay $ utctDay time+           !dayTime = fromInteger $ diffTimeToPicoseconds $ utctDayTime time+       pure (day, dayTime) #else-        withCString f $ \f' ->-        alloca $ \secPtr ->-        alloca $ \nsecPtr -> do-            Posix.throwErrnoPathIfMinus1Retry_ "getmodtime" f $ c_getModTime f' secPtr nsecPtr-            CTime sec <- peek secPtr-            CLong nsec <- peek nsecPtr-            pure (sec, nsec)+    withCString f $ \f' ->+    alloca $ \secPtr ->+    alloca $ \nsecPtr -> do+        Posix.throwErrnoPathIfMinus1Retry_ "getmodtime" f $ c_getModTime f' secPtr nsecPtr+        CTime sec <- peek secPtr+        CLong nsec <- peek nsecPtr+        pure (sec, nsec)  -- Sadly even unix’s getFileStatus + modificationTimeHiRes is still about twice as slow -- as doing the FFI call ourselves :(.@@ -150,11 +160,14 @@  modificationTime :: FileVersion -> Maybe UTCTime modificationTime VFSVersion{} = Nothing-modificationTime (ModificationTime large small) =+modificationTime (ModificationTime large small) = Just $ internalTimeToUTCTime large small++internalTimeToUTCTime :: Int64 -> Int64 -> UTCTime+internalTimeToUTCTime large small = #ifdef mingw32_HOST_OS-    Just (UTCTime (ModifiedJulianDay $ fromIntegral large) (picosecondsToDiffTime $ fromIntegral small))+    UTCTime (ModifiedJulianDay $ fromIntegral large) (picosecondsToDiffTime $ fromIntegral small) #else-    Just (systemToUTCTime $ MkSystemTime large (fromIntegral small))+    systemToUTCTime $ MkSystemTime large (fromIntegral small) #endif  getFileContentsRule :: VFSHandle -> Rules ()@@ -180,7 +193,15 @@ getFileContents :: NormalizedFilePath -> Action (UTCTime, Maybe T.Text) getFileContents f = do     (fv, txt) <- use_ GetFileContents f-    modTime <- maybe (liftIO getCurrentTime) return $ modificationTime fv+    modTime <- case modificationTime fv of+      Just t -> pure t+      Nothing -> do+        foi <- use_ IsFileOfInterest f+        liftIO $ case foi of+          IsFOI Modified -> getCurrentTime+          _ -> do+            (large,small) <- getModTime $ fromNormalizedFilePath f+            pure $ internalTimeToUTCTime large small     return (modTime, txt)  fileStoreRules :: VFSHandle -> Rules ()@@ -188,31 +209,40 @@     addIdeGlobal vfs     getModificationTimeRule vfs     getFileContentsRule vfs----- | Notify the compiler service that a particular file has been modified.---   Use 'Nothing' to say the file is no longer in the virtual file system---   but should be sourced from disk, or 'Just' to give its new value.-setBufferModified :: IdeState -> NormalizedFilePath -> Maybe T.Text -> IO ()-setBufferModified state absFile contents = do-    VFSHandle{..} <- getIdeGlobalState state-    whenJust setVirtualFileContents $ \set ->-        set (filePathToUri' absFile) contents-    void $ shakeRestart state [kick]+    isFileOfInterestRule  -- | Note that some buffer for a specific file has been modified but not -- with what changes.-setFileModified :: IdeState -> NormalizedFilePath -> IO ()-setFileModified state nfp = do+setFileModified :: IdeState+                -> Bool -- ^ Was the file saved?+                -> NormalizedFilePath+                -> IO ()+setFileModified state saved nfp = do+    ideOptions <- getIdeOptionsIO $ shakeExtras state+    let checkParents = case optCheckParents ideOptions of+          AlwaysCheck -> True+          CheckOnSaveAndClose -> saved+          _ -> False     VFSHandle{..} <- getIdeGlobalState state     when (isJust setVirtualFileContents) $-        fail "setSomethingModified can't be called on this type of VFSHandle"-    let da = mkDelayedAction "FileStoreTC" L.Info $ do-          ShakeExtras{progressUpdate} <- getShakeExtras-          liftIO $ progressUpdate KickStarted-          void $ use GetSpanInfo nfp-          liftIO $ progressUpdate KickCompleted-    shakeRestart state [da]+        fail "setFileModified can't be called on this type of VFSHandle"+    shakeRestart state [kick]+    when checkParents $+      typecheckParents state nfp++typecheckParents :: IdeState -> NormalizedFilePath -> IO ()+typecheckParents state nfp = void $ shakeEnqueue (shakeExtras state) parents+  where parents = mkDelayedAction "ParentTC" L.Debug (typecheckParentsAction nfp)++typecheckParentsAction :: NormalizedFilePath -> Action ()+typecheckParentsAction nfp = do+    revs <- reverseDependencies nfp <$> useNoFile_ GetModuleGraph+    logger <- logger <$> getShakeExtras+    let log = L.logInfo logger . T.pack+    liftIO $ do+      (log $ "Typechecking reverse dependencies for" ++ show nfp ++ ": " ++ show revs)+        `catch` \(e :: SomeException) -> log (show e)+    () <$ uses GetModIface revs  -- | Note that some buffer somewhere has been modified, but don't say what. --   Only valid if the virtual file system was initialised by LSP, as that
src/Development/IDE/Core/OfInterest.hs view
@@ -9,7 +9,7 @@ module Development.IDE.Core.OfInterest(     ofInterestRules,     getFilesOfInterest, setFilesOfInterest, modifyFilesOfInterest,-    kick+    kick, FileOfInterestStatus(..)     ) where  import Control.Concurrent.Extra@@ -20,22 +20,24 @@ import Data.Typeable import qualified Data.ByteString.UTF8 as BS import Control.Exception-import Data.HashSet (HashSet)-import qualified Data.HashSet as HashSet+import Data.HashMap.Strict (HashMap)+import qualified Data.HashMap.Strict as HashMap import qualified Data.Text as T import Data.Tuple.Extra import Development.Shake +import Development.IDE.Types.Exports import Development.IDE.Types.Location import Development.IDE.Types.Logger import Development.IDE.Core.RuleTypes import Development.IDE.Core.Shake-import Control.Monad+import Data.Maybe (mapMaybe)+import GhcPlugins (HomeModInfo(hm_iface)) -newtype OfInterestVar = OfInterestVar (Var (HashSet NormalizedFilePath))+newtype OfInterestVar = OfInterestVar (Var (HashMap NormalizedFilePath FileOfInterestStatus)) instance IsIdeGlobal OfInterestVar -type instance RuleResult GetFilesOfInterest = HashSet NormalizedFilePath+type instance RuleResult GetFilesOfInterest = HashMap NormalizedFilePath FileOfInterestStatus  data GetFilesOfInterest = GetFilesOfInterest     deriving (Eq, Show, Typeable, Generic)@@ -47,7 +49,7 @@ -- | The rule that initialises the files of interest state. ofInterestRules :: Rules () ofInterestRules = do-    addIdeGlobal . OfInterestVar =<< liftIO (newVar HashSet.empty)+    addIdeGlobal . OfInterestVar =<< liftIO (newVar HashMap.empty)     defineEarlyCutoff $ \GetFilesOfInterest _file -> assert (null $ fromNormalizedFilePath _file) $ do         alwaysRerun         filesOfInterest <- getFilesOfInterestUntracked@@ -55,7 +57,7 @@   -- | Get the files that are open in the IDE.-getFilesOfInterest :: Action (HashSet NormalizedFilePath)+getFilesOfInterest :: Action (HashMap NormalizedFilePath FileOfInterestStatus) getFilesOfInterest = useNoFile_ GetFilesOfInterest  @@ -65,23 +67,24 @@  -- | Set the files-of-interest - not usually necessary or advisable. --   The LSP client will keep this information up to date.-setFilesOfInterest :: IdeState -> HashSet NormalizedFilePath -> IO ()+setFilesOfInterest :: IdeState -> HashMap NormalizedFilePath FileOfInterestStatus -> IO () setFilesOfInterest state files = modifyFilesOfInterest state (const files) -getFilesOfInterestUntracked :: Action (HashSet NormalizedFilePath)+getFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus) getFilesOfInterestUntracked = do     OfInterestVar var <- getIdeGlobalAction     liftIO $ readVar var  -- | Modify the files-of-interest - not usually necessary or advisable. --   The LSP client will keep this information up to date.-modifyFilesOfInterest :: IdeState -> (HashSet NormalizedFilePath -> HashSet NormalizedFilePath) -> IO ()+modifyFilesOfInterest+  :: IdeState+  -> (HashMap NormalizedFilePath FileOfInterestStatus -> HashMap NormalizedFilePath FileOfInterestStatus)+  -> IO () modifyFilesOfInterest state f = do     OfInterestVar var <- getIdeGlobalState state     files <- modifyVar var $ pure . dupe . f-    logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show $ HashSet.toList files)-    let das = map (\nfp -> mkDelayedAction "OfInterest" Debug (use GetSpanInfo nfp)) (HashSet.toList files)-    shakeRestart state das+    logDebug (ideLogger state) $ "Set files of interest to: " <> T.pack (show $ HashMap.toList files)  -- | Typecheck all the files of interest. --   Could be improved@@ -90,5 +93,12 @@     files <- getFilesOfInterest     ShakeExtras{progressUpdate} <- getShakeExtras     liftIO $ progressUpdate KickStarted-    void $ uses TypeCheck $ HashSet.toList files++    -- Update the exports map for the project+    results <-  uses TypeCheck $ HashMap.keys files+    ShakeExtras{exportsMap} <- getShakeExtras+    let modIfaces = mapMaybe (fmap (hm_iface . tmrModInfo)) results+        !exportsMap' = createExportsMap modIfaces+    liftIO $ modifyVar_ exportsMap $ evaluate . (exportsMap' <>)+     liftIO $ progressUpdate KickCompleted
src/Development/IDE/Core/PositionMapping.hs view
@@ -2,6 +2,10 @@ -- SPDX-License-Identifier: Apache-2.0 module Development.IDE.Core.PositionMapping   ( PositionMapping(..)+  , PositionResult(..)+  , lowerRange+  , upperRange+  , positionResultToMaybe   , fromCurrentPosition   , toCurrentPosition   , PositionDelta(..)@@ -21,17 +25,50 @@ import Language.Haskell.LSP.Types import Data.List +-- | Either an exact position, or the range of text that was substituted+data PositionResult a+  = PositionRange -- ^ Fields need to be non-strict otherwise bind is exponential+  { unsafeLowerRange :: a+  , unsafeUpperRange :: a }+  | PositionExact !a+  deriving (Eq,Ord,Show,Functor)++lowerRange :: PositionResult a -> a+lowerRange (PositionExact a) = a+lowerRange (PositionRange lower _) = lower++upperRange :: PositionResult a -> a+upperRange (PositionExact a) = a+upperRange (PositionRange _ upper) = upper++positionResultToMaybe :: PositionResult a -> Maybe a+positionResultToMaybe (PositionExact a) = Just a+positionResultToMaybe _ = Nothing++instance Applicative PositionResult where+  pure = PositionExact+  (PositionExact f) <*> a = fmap f a+  (PositionRange f g) <*> (PositionExact a) = PositionRange (f a) (g a)+  (PositionRange f g) <*> (PositionRange lower upper) = PositionRange (f lower) (g upper)++instance Monad PositionResult where+  (PositionExact a) >>= f = f a+  (PositionRange lower upper) >>= f = PositionRange lower' upper'+    where+      lower' = lowerRange $ f lower+      upper' = upperRange $ f upper+ -- The position delta is the difference between two versions data PositionDelta = PositionDelta-  { toDelta :: !(Position -> Maybe Position)-  , fromDelta :: !(Position -> Maybe Position)+  { toDelta :: !(Position -> PositionResult Position)+  , fromDelta :: !(Position -> PositionResult Position)   }  fromCurrentPosition :: PositionMapping -> Position -> Maybe Position-fromCurrentPosition (PositionMapping pm) = fromDelta pm+fromCurrentPosition (PositionMapping pm) = positionResultToMaybe . fromDelta pm  toCurrentPosition :: PositionMapping -> Position -> Maybe Position-toCurrentPosition (PositionMapping pm) = toDelta pm+toCurrentPosition (PositionMapping pm) = positionResultToMaybe . toDelta pm  -- A position mapping is the difference from the current version to -- a specific version@@ -59,7 +96,7 @@                 (from1 >=> from2)  idDelta :: PositionDelta-idDelta = PositionDelta Just Just+idDelta = PositionDelta pure pure  -- | Convert a set of changes into a delta from k  to k + 1 mkDelta :: [TextDocumentContentChangeEvent] -> PositionDelta@@ -76,16 +113,16 @@     } applyChange posMapping _ = posMapping -toCurrent :: Range -> T.Text -> Position -> Maybe Position-toCurrent (Range (Position startLine startColumn) (Position endLine endColumn)) t (Position line column)+toCurrent :: Range -> T.Text -> Position -> PositionResult Position+toCurrent (Range start@(Position startLine startColumn) end@(Position endLine endColumn)) t (Position line column)     | line < startLine || line == startLine && column < startColumn =       -- Position is before the change and thereby unchanged.-      Just $ Position line column+      PositionExact $ Position line column     | line > endLine || line == endLine && column >= endColumn =       -- Position is after the change so increase line and column number       -- as necessary.-      Just $ Position (line + lineDiff) newColumn-    | otherwise = Nothing+      PositionExact $ Position newLine newColumn+    | otherwise = PositionRange start end     -- Position is in the region that was changed.     where         lineDiff = linesNew - linesOld@@ -94,20 +131,21 @@         newEndColumn           | linesNew == 0 = startColumn + T.length t           | otherwise = T.length $ T.takeWhileEnd (/= '\n') t-        newColumn+        !newColumn           | line == endLine = column + newEndColumn - endColumn           | otherwise = column+        !newLine = line + lineDiff -fromCurrent :: Range -> T.Text -> Position -> Maybe Position-fromCurrent (Range (Position startLine startColumn) (Position endLine endColumn)) t (Position line column)+fromCurrent :: Range -> T.Text -> Position -> PositionResult Position+fromCurrent (Range start@(Position startLine startColumn) end@(Position endLine endColumn)) t (Position line column)     | line < startLine || line == startLine && column < startColumn =       -- Position is before the change and thereby unchanged-      Just $ Position line column+      PositionExact $ Position line column     | line > newEndLine || line == newEndLine && column >= newEndColumn =       -- Position is after the change so increase line and column number       -- as necessary.-      Just $ Position (line - lineDiff) newColumn-    | otherwise = Nothing+      PositionExact $ Position newLine newColumn+    | otherwise = PositionRange start end     -- Position is in the region that was changed.     where         lineDiff = linesNew - linesOld@@ -117,6 +155,7 @@         newEndColumn           | linesNew == 0 = startColumn + T.length t           | otherwise = T.length $ T.takeWhileEnd (/= '\n') t-        newColumn+        !newColumn           | line == newEndLine = column - (newEndColumn - endColumn)           | otherwise = column+        !newLine = line - lineDiff
src/Development/IDE/Core/Preprocessor.hs view
@@ -37,8 +37,8 @@  -- | Given a file and some contents, apply any necessary preprocessors, --   e.g. unlit/cpp. Return the resulting buffer and the DynFlags it implies.-preprocessor :: (ExceptionMonad m, HasDynFlags m, MonadIO m) => FilePath -> Maybe StringBuffer -> ExceptT [FileDiagnostic] m (StringBuffer, DynFlags)-preprocessor filename mbContents = do+preprocessor :: (ExceptionMonad m, HasDynFlags m, MonadIO m) => HscEnv -> FilePath -> Maybe StringBuffer -> ExceptT [FileDiagnostic] m (StringBuffer, DynFlags)+preprocessor env filename mbContents = do     -- Perform unlit     (isOnDisk, contents) <-         if isLiterate filename then do@@ -51,7 +51,7 @@             return (isOnDisk, contents)      -- Perform cpp-    dflags  <- ExceptT $ parsePragmasIntoDynFlags filename contents+    dflags  <- ExceptT $ parsePragmasIntoDynFlags env filename contents     (isOnDisk, contents, dflags) <-         if not $ xopt LangExt.Cpp dflags then             return (isOnDisk, contents, dflags)@@ -68,7 +68,7 @@                                   [] -> throw e                                   diags -> return $ Left diags                             )-            dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents+            dflags <- ExceptT $ parsePragmasIntoDynFlags env filename contents             return (False, contents, dflags)      -- Perform preprocessor@@ -76,7 +76,7 @@         return (contents, dflags)     else do         contents <- liftIO $ runPreprocessor dflags filename $ if isOnDisk then Nothing else Just contents-        dflags <- ExceptT $ parsePragmasIntoDynFlags filename contents+        dflags <- ExceptT $ parsePragmasIntoDynFlags env filename contents         return (contents, dflags)   where     logAction :: IORef [CPPLog] -> LogAction@@ -108,8 +108,8 @@     -- 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]+    go acc (CPPLog sev (RealSrcSpan span) msg : logs) =+      let diag = CPPDiag (realSrcSpanToRange 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@@ -134,10 +134,11 @@ -- | This reads the pragma information directly from the provided buffer. parsePragmasIntoDynFlags     :: (ExceptionMonad m, HasDynFlags m, MonadIO m)-    => FilePath+    => HscEnv+    -> FilePath     -> SB.StringBuffer     -> m (Either [FileDiagnostic] DynFlags)-parsePragmasIntoDynFlags fp contents = catchSrcErrors "pragmas" $ do+parsePragmasIntoDynFlags env fp contents = catchSrcErrors "pragmas" $ do     dflags0  <- getDynFlags     let opts = Hdr.getOptions dflags0 contents fp @@ -145,8 +146,8 @@     liftIO $ evaluate $ rnf opts      (dflags, _, _) <- parseDynamicFilePragma dflags0 opts-    return dflags-+    dflags' <- liftIO $ initializePlugins env dflags+    return $ disableWarningsAsErrors dflags'  -- | Run (unlit) literate haskell preprocessor on a file, or buffer if set runLhs :: DynFlags -> FilePath -> Maybe SB.StringBuffer -> IO SB.StringBuffer
src/Development/IDE/Core/RuleTypes.hs view
@@ -16,6 +16,7 @@ import           Development.IDE.Import.DependencyInformation import Development.IDE.GHC.Compat import Development.IDE.GHC.Util+import Development.IDE.Core.Shake (KnownTargets) import           Data.Hashable import           Data.Typeable import qualified Data.Set as S@@ -46,11 +47,24 @@ -- This rule is also responsible for calling ReportImportCycles for each file in the transitive closure. type instance RuleResult GetDependencies = TransitiveDependencies +type instance RuleResult GetModuleGraph = DependencyInformation++data GetKnownTargets = GetKnownTargets+  deriving (Show, Generic, Eq, Ord)+instance Hashable GetKnownTargets+instance NFData   GetKnownTargets+instance Binary   GetKnownTargets+type instance RuleResult GetKnownTargets = KnownTargets+ -- | Contains the typechecked module and the OrigNameCache entry for -- that module. data TcModuleResult = TcModuleResult     { tmrModule     :: TypecheckedModule+    -- ^ warning, the ModIface in the tm_checked_module_info of the+    -- TypecheckedModule will always be Nothing, use the ModIface in the+    -- HomeModInfo instead     , tmrModInfo    :: HomeModInfo+    , tmrDeferedError :: !Bool -- ^ Did we defer any type errors for this module?     } instance Show TcModuleResult where     show = show . pm_mod_summary . tm_parsed_module . tmrModule@@ -117,8 +131,20 @@ -- | Get a module interface details, either from an interface file or a typechecked module type instance RuleResult GetModIface = HiFileResult -type instance RuleResult IsFileOfInterest = Bool+data FileOfInterestStatus = OnDisk | Modified+  deriving (Eq, Show, Typeable, Generic)+instance Hashable FileOfInterestStatus+instance NFData   FileOfInterestStatus+instance Binary   FileOfInterestStatus +data IsFileOfInterestResult = NotFOI | IsFOI FileOfInterestStatus+  deriving (Eq, Show, Typeable, Generic)+instance Hashable IsFileOfInterestResult+instance NFData   IsFileOfInterestResult+instance Binary   IsFileOfInterestResult++type instance RuleResult IsFileOfInterest = IsFileOfInterestResult+ -- | Generate a ModSummary that has enough information to be used to get .hi and .hie files. -- without needing to parse the entire source type instance RuleResult GetModSummary = ModSummary@@ -144,6 +170,12 @@ instance Hashable GetDependencyInformation instance NFData   GetDependencyInformation instance Binary   GetDependencyInformation++data GetModuleGraph = GetModuleGraph+    deriving (Eq, Show, Typeable, Generic)+instance Hashable GetModuleGraph+instance NFData   GetModuleGraph+instance Binary   GetModuleGraph  data ReportImportCycles = ReportImportCycles     deriving (Eq, Show, Typeable, Generic)
src/Development/IDE/Core/Rules.hs view
@@ -58,6 +58,7 @@ import Data.List import qualified Data.Set                                 as Set import qualified Data.Text                                as T+import qualified Data.Text.Encoding                       as T import           Development.IDE.GHC.Error import           Development.Shake                        hiding (Diagnostic) import Development.IDE.Core.RuleTypes@@ -87,6 +88,9 @@ import FastString (FastString(uniq)) import qualified HeaderInfo as Hdr import Data.Time (UTCTime(..))+import Data.Hashable+import qualified Data.HashSet as HashSet+import qualified Data.HashMap.Strict as HM  -- | This is useful for rules to convert rules that can only produce errors or -- a result into the more general IdeResult type that supports producing@@ -186,11 +190,18 @@       wait <- lift $ delayedAction $ mkDelayedAction "OutOfDateHie" L.Info $ do         hsc <- hscEnv <$> use_ GhcSession f         pm <- use_ GetParsedModule f-        typeCheckRuleDefinition hsc pm DoGenerateInterfaceFiles+        source <- getSourceFileSource f+        typeCheckRuleDefinition hsc pm NotFOI (Just source)       _ <- MaybeT $ liftIO $ timeout 1 wait       ncu <- mkUpdater       liftIO $ loadHieFile ncu hie_f +getSourceFileSource :: NormalizedFilePath -> Action BS.ByteString+getSourceFileSource nfp = do+    (_, msource) <- getFileContents nfp+    case msource of+        Nothing -> liftIO $ BS.readFile (fromNormalizedFilePath nfp)+        Just source -> pure $ T.encodeUtf8 source  getPackageHieFile :: ShakeExtras                   -> Module             -- ^ Package Module to load .hie file for@@ -231,6 +242,12 @@ priorityFilesOfInterest :: Priority priorityFilesOfInterest = Priority (-2) +-- | IMPORTANT FOR HLINT INTEGRATION:+-- We currently parse the module both with and without Opt_Haddock, and+-- return the one with Haddocks if it -- succeeds. However, this may not work+-- for hlint, and we might need to save the one without haddocks too.+-- See https://github.com/digital-asset/ghcide/pull/350#discussion_r370878197+-- and https://github.com/mpickering/ghcide/pull/22#issuecomment-625070490 getParsedModuleRule :: Rules () getParsedModuleRule = defineEarlyCutoff $ \GetParsedModule file -> do     sess <- use_ GhcSession file@@ -249,18 +266,28 @@         then             liftIO mainParse         else do-            let haddockParse = do-                    (_, (!diagsHaddock, _)) <--                        getParsedModuleDefinition (withOptHaddock hsc) opt comp_pkgs file modTime contents-                    return diagsHaddock+            let haddockParse = getParsedModuleDefinition (withOptHaddock hsc) opt comp_pkgs file modTime contents -            ((fingerPrint, (diags, res)), diagsHaddock) <--                -- parse twice, with and without Haddocks, concurrently-                -- we want warnings if parsing with Haddock fails-                -- but if we parse with Haddock we lose annotations-                liftIO $ concurrently mainParse haddockParse+            -- parse twice, with and without Haddocks, concurrently+            -- we cannot ignore Haddock parse errors because files of+            -- non-interest are always parsed with Haddocks+            -- If we can parse Haddocks, might as well use them+            --+            -- HLINT INTEGRATION: might need to save the other parsed module too+            ((fp,(diags,res)),(fph,(diagsh,resh))) <- liftIO $ concurrently mainParse haddockParse -            return (fingerPrint, (mergeParseErrorsHaddock diags diagsHaddock, res))+            -- Merge haddock and regular diagnostics so we can always report haddock+            -- parse errors+            let diagsM = mergeParseErrorsHaddock diags diagsh+            case resh of+              Just _+                | HaddockParse <- optHaddockParse opt+                -> pure (fph, (diagsM, resh))+              -- If we fail to parse haddocks, report the haddock diagnostics as well and+              -- return the non-haddock parse.+              -- This seems to be the correct behaviour because the Haddock flag is added+              -- by us and not the user, so our IDE shouldn't stop working because of it.+              _ -> pure (fp, (diagsM, res))   withOptHaddock :: HscEnv -> HscEnv@@ -279,7 +306,6 @@     fixMessage x | "parse error " `T.isPrefixOf` x = "Haddock " <> x                  | otherwise = "Haddock: " <> x - getParsedModuleDefinition :: HscEnv -> IdeOptions -> [PackageName] -> NormalizedFilePath -> UTCTime -> Maybe T.Text -> IO (Maybe ByteString, ([FileDiagnostic], Maybe ParsedModule)) getParsedModuleDefinition packageState opt comp_pkgs file modTime contents = do     let fp = fromNormalizedFilePath file@@ -297,14 +323,23 @@ getLocatedImportsRule =     define $ \GetLocatedImports file -> do         ms <- use_ GetModSummaryWithoutTimestamps file+        targets <- useNoFile_ GetKnownTargets         let imports = [(False, imp) | imp <- ms_textual_imps ms] ++ [(True, imp) | imp <- ms_srcimps ms]         env_eq <- use_ GhcSession file-        let env = hscEnv env_eq+        let env = hscEnvWithImportPaths env_eq         let import_dirs = deps env_eq-        let dflags = addRelativeImport file (moduleName $ ms_mod ms) $ hsc_dflags env+        let dflags = hsc_dflags env+            isImplicitCradle = isNothing $ envImportPaths env_eq+        dflags <- return $ if isImplicitCradle+                    then addRelativeImport file (moduleName $ ms_mod ms) dflags+                    else dflags         opt <- getIdeOptions+        let getTargetExists modName nfp+                | isImplicitCradle = getFileExists nfp+                | HM.member modName targets = getFileExists nfp+                | otherwise = return False         (diags, imports') <- fmap unzip $ forM imports $ \(isSource, (mbPkgName, modName)) -> do-            diagOrImp <- locateModule dflags import_dirs (optExtensions opt) getFileExists modName mbPkgName isSource+            diagOrImp <- locateModule dflags import_dirs (optExtensions opt) getTargetExists modName mbPkgName isSource             case diagOrImp of                 Left diags -> pure (diags, Left (modName, Nothing))                 Right (FileImport path) -> pure ([], Left (modName, Just path))@@ -442,7 +477,7 @@             | f `elem` fs = Just (imp, fs)           cycleErrorInFile _ _ = Nothing           toDiag imp mods = (fp , ShowDiag , ) $ Diagnostic-            { _range = (_range :: Location -> Range) loc+            { _range = rng             , _severity = Just DsError             , _source = Just "Import cycle detection"             , _message = "Cyclic module dependency between " <> showCycle mods@@ -450,8 +485,8 @@             , _relatedInformation = Nothing             , _tags = Nothing             }-            where loc = srcSpanToLocation (getLoc imp)-                  fp = toNormalizedFilePath' $ srcSpanToFilename (getLoc imp)+            where rng = fromMaybe noRange $ srcSpanToRange (getLoc imp)+                  fp = toNormalizedFilePath' $ fromMaybe noFilePath $ srcSpanToFilename (getLoc imp)           getModuleName file = do            ms <- use_ GetModSummaryWithoutTimestamps file            pure (moduleNameString . moduleName . ms_mod $ ms)@@ -498,13 +533,22 @@     hsc  <- hscEnv <$> use_ GhcSessionDeps file     -- do not generate interface files as this rule is called     -- for files of interest on every keystroke-    typeCheckRuleDefinition hsc pm SkipGenerationOfInterfaceFiles+    source <- getSourceFileSource file+    isFoi <- use_ IsFileOfInterest file+    typeCheckRuleDefinition hsc pm isFoi (Just source) -data GenerateInterfaceFiles-    = DoGenerateInterfaceFiles-    | SkipGenerationOfInterfaceFiles-    deriving (Show)+knownFilesRule :: Rules ()+knownFilesRule = defineEarlyCutOffNoFile $ \GetKnownTargets -> do+  alwaysRerun+  fs <- knownTargets+  pure (BS.pack (show $ hash fs), unhashed fs) +getModuleGraphRule :: Rules ()+getModuleGraphRule = defineNoFile $ \GetModuleGraph -> do+  fs <- toKnownFiles <$> useNoFile_ GetKnownTargets+  rawDepInfo <- rawDependencyInformation (HashSet.toList fs)+  pure $ processDependencyInformation rawDepInfo+ -- This is factored out so it can be directly called from the GetModIface -- rule. Directly calling this rule means that on the initial load we can -- garbage collect all the intermediate typechecked modules rather than@@ -512,19 +556,28 @@ typeCheckRuleDefinition     :: HscEnv     -> ParsedModule-    -> GenerateInterfaceFiles -- ^ Should generate .hi and .hie files ?+    -> IsFileOfInterestResult -- ^ Should generate .hi and .hie files ?+    -> Maybe BS.ByteString     -> Action (IdeResult TcModuleResult)-typeCheckRuleDefinition hsc pm generateArtifacts = do+typeCheckRuleDefinition hsc pm isFoi source = do   setPriority priorityTypeCheck   IdeOptions { optDefer = defer } <- getIdeOptions    addUsageDependencies $ liftIO $ do     res <- typecheckModule defer hsc pm     case res of-      (diags, Just (hsc,tcm)) | DoGenerateInterfaceFiles <- generateArtifacts -> do-        diagsHie <- generateAndWriteHieFile hsc (tmrModule tcm)-        diagsHi  <- generateAndWriteHiFile hsc tcm-        return (diags <> diagsHi <> diagsHie, Just tcm)+      (diags, Just (hsc,tcm)) -> do+        case isFoi of+          IsFOI Modified -> return (diags, Just tcm)+          _ -> do -- If the file is saved on disk, or is not a FOI, we write out ifaces+            diagsHie <- generateAndWriteHieFile hsc (tmrModule tcm) (fromMaybe "" source)+            -- Don't save interface files for modules that compiled due to defering+            -- type errors, as we won't get proper diagnostics if we load these from+            -- disk+            diagsHi  <- if not $ tmrDeferedError tcm+                        then writeHiFile hsc tcm+                        else pure mempty+            return (diags <> diagsHi <> diagsHie, Just tcm)       (diags, res) ->         return (diags, snd <$> res)  where@@ -636,7 +689,7 @@             setupFinderCache (map hirModSummary ifaces)             mapM_ (uncurry loadDepModule) inLoadOrder -        res <- liftIO $ newHscEnvEq session' []+        res <- liftIO $ newHscEnvEq "" session' []         return ([], Just res)  where   unpack HiFileResult{..} bc = (hirModIface, bc)@@ -662,9 +715,7 @@ isHiFileStableRule = define $ \IsHiFileStable f -> do     ms <- use_ GetModSummaryWithoutTimestamps f     let hiFile = toNormalizedFilePath'-                $ case ms_hsc_src ms of-                    HsBootFile -> addBootSuffix (ml_hi_file $ ms_location ms)-                    _ -> ml_hi_file $ ms_location ms+                $ ml_hi_file $ ms_location ms     mbHiVersion <- use  GetModificationTime_{missingFileDiagnostics=False} hiFile     modVersion  <- use_ GetModificationTime f     sourceModified <- case mbHiVersion of@@ -684,11 +735,12 @@ getModSummaryRule :: Rules () getModSummaryRule = do     defineEarlyCutoff $ \GetModSummary f -> do-        dflags <- hsc_dflags . hscEnv <$> use_ GhcSession f+        session <- hscEnv <$> use_ GhcSession f+        let dflags = hsc_dflags session         (modTime, mFileContent) <- getFileContents f         let fp = fromNormalizedFilePath f         modS <- liftIO $ evalWithDynFlags dflags $ runExceptT $-                getModSummaryFromImports fp modTime (textToStringBuffer <$> mFileContent)+                getModSummaryFromImports session fp modTime (textToStringBuffer <$> mFileContent)         case modS of             Right ms -> do                 let fingerPrint = hash (computeFingerprint f dflags ms, hashUTC modTime)@@ -726,18 +778,18 @@ getModIfaceRule :: Rules () getModIfaceRule = defineEarlyCutoff $ \GetModIface f -> do #if MIN_GHC_API_VERSION(8,6,0) && !defined(GHC_LIB)-    fileOfInterest <- use_ IsFileOfInterest f-    if fileOfInterest-        then do-            -- Never load from disk for files of interest-            tmr <- use TypeCheck f-            let !hiFile = extractHiFileResult tmr-            let fp = hiFileFingerPrint <$> hiFile-            return (fp, ([], hiFile))-        else do-            hiFile <- use GetModIfaceFromDisk f-            let fp = hiFileFingerPrint <$> hiFile-            return (fp, ([], hiFile))+  fileOfInterest <- use_ IsFileOfInterest f+  case fileOfInterest of+    IsFOI _ -> do+      -- Never load from disk for files of interest+      tmr <- use TypeCheck f+      let !hiFile = extractHiFileResult tmr+      let fp = hiFileFingerPrint <$> hiFile+      return (fp, ([], hiFile))+    NotFOI -> do+      hiFile <- use GetModIfaceFromDisk f+      let fp = hiFileFingerPrint <$> hiFile+      return (fp, ([], hiFile)) #else     tm <- use TypeCheck f     let !hiFile = extractHiFileResult tm@@ -765,9 +817,10 @@     case mb_pm of         Nothing -> return (diags, Nothing)         Just pm -> do+            source <- getSourceFileSource f             -- Invoke typechecking directly to update it without incurring a dependency             -- on the parsed module and the typecheck rules-            (diags', tmr) <- typeCheckRuleDefinition hsc pm DoGenerateInterfaceFiles+            (diags', tmr) <- typeCheckRuleDefinition hsc pm NotFOI (Just source)             -- Bang pattern is important to avoid leaking 'tmr'             let !res = extractHiFileResult tmr             return (diags <> diags', res)@@ -778,12 +831,6 @@     -- Bang patterns are important to force the inner fields     Just $! tmr_hiFileResult tmr -isFileOfInterestRule :: Rules ()-isFileOfInterestRule = defineEarlyCutoff $ \IsFileOfInterest f -> do-    filesOfInterest <- getFilesOfInterest-    let res = f `elem` filesOfInterest-    return (Just (if res then "1" else ""), ([], Just res))- -- | A rule that wires per-file rules together mainRule :: Rules () mainRule = do@@ -799,9 +846,10 @@     loadGhcSession     getModIfaceFromDiskRule     getModIfaceRule-    isFileOfInterestRule     getModSummaryRule     isHiFileStableRule+    getModuleGraphRule+    knownFilesRule  -- | Given the path to a module src file, this rule returns True if the -- corresponding `.hi` file is stable, that is, if it is newer
src/Development/IDE/Core/Service.hs view
@@ -83,4 +83,4 @@ -- e.g., the ofInterestRule. runAction :: String -> IdeState -> Action a -> IO a runAction herald ide act =-  join $ shakeEnqueue ide (mkDelayedAction herald Logger.Info act)+  join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Info act)
src/Development/IDE/Core/Shake.hs view
@@ -24,6 +24,7 @@ module Development.IDE.Core.Shake(     IdeState, shakeExtras,     ShakeExtras(..), getShakeExtras, getShakeExtrasRules,+    KnownTargets, toKnownFiles,     IdeRule, IdeResult,     GetModificationTime(GetModificationTime, GetModificationTime_, missingFileDiagnostics),     shakeOpen, shakeShut,@@ -44,6 +45,7 @@     getIdeOptionsIO,     GlobalIdeOptions(..),     garbageCollect,+    knownTargets,     setPriority,     sendEvent,     ideLogger,@@ -66,31 +68,35 @@ import           Development.Shake.Database import           Development.Shake.Classes import           Development.Shake.Rule+import           Data.HashMap.Strict (HashMap) import qualified Data.HashMap.Strict as HMap import qualified Data.Map.Strict as Map import qualified Data.ByteString.Char8 as BS import           Data.Dynamic import           Data.Maybe-import Data.Map.Strict (Map)+import           Data.Map.Strict (Map) import           Data.List.Extra (partition, takeEnd)+import           Data.HashSet (HashSet) import qualified Data.Set as Set import qualified Data.Text as T import Data.Tuple.Extra import Data.Unique import Development.IDE.Core.Debouncer-import Development.IDE.GHC.Compat ( NameCacheUpdater(..), upNameCache )+import Development.IDE.GHC.Compat (ModuleName, NameCacheUpdater(..), upNameCache )+import Development.IDE.GHC.Orphans () import Development.IDE.Core.PositionMapping+import Development.IDE.Types.Action import Development.IDE.Types.Logger hiding (Priority) import qualified Development.IDE.Types.Logger as Logger import Language.Haskell.LSP.Diagnostics import qualified Data.SortedList as SL import           Development.IDE.Types.Diagnostics+import Development.IDE.Types.Exports import Development.IDE.Types.Location import Development.IDE.Types.Options import           Control.Concurrent.Async import           Control.Concurrent.Extra-import           Control.Concurrent.STM.TQueue (flushTQueue, writeTQueue, readTQueue, newTQueue, TQueue)-import           Control.Concurrent.STM (readTVar, writeTVar, newTVarIO, TVar, atomically)+import           Control.Concurrent.STM (readTVar, writeTVar, newTVarIO, atomically) import           Control.DeepSeq import           Control.Exception.Extra import           System.Time.Extra@@ -105,18 +111,19 @@ import           GHC.Generics import           System.IO.Unsafe import Language.Haskell.LSP.Types-import Data.Foldable (traverse_) import qualified Control.Monad.STM as STM import Control.Monad.IO.Class import Control.Monad.Reader import Control.Monad.Trans.Maybe import Data.Traversable+import Data.Hashable  import Data.IORef import NameCache import UniqSupply import PrelInfo import Data.Int (Int64)+import qualified Data.HashSet as HSet  -- information we stash inside the shakeExtra field data ShakeExtras = ShakeExtras@@ -148,9 +155,21 @@     ,withIndefiniteProgress :: WithIndefiniteProgressFunc     -- ^ Same as 'withProgress', but for processes that do not report the percentage complete     ,restartShakeSession :: [DelayedAction ()] -> IO ()-    , ideNc :: IORef NameCache+    ,ideNc :: IORef NameCache+    -- | A mapping of module name to known target (or candidate targets, if missing)+    ,knownTargetsVar :: Var (Hashed KnownTargets)+    -- | A mapping of exported identifiers for local modules. Updated on kick+    ,exportsMap :: Var ExportsMap+    -- | A work queue for actions added via 'runInShakeSession'+    ,actionQueue :: ActionQueue     } +-- | A mapping of module name to known files+type KnownTargets = HashMap ModuleName [NormalizedFilePath]++toKnownFiles :: KnownTargets -> HashSet NormalizedFilePath+toKnownFiles = HSet.fromList . concat . HMap.elems+ type WithProgressFunc = forall a.     T.Text -> LSP.ProgressCancellable -> ((LSP.Progress -> IO ()) -> IO a) -> IO a type WithIndefiniteProgressFunc = forall a.@@ -288,11 +307,9 @@  -- | A live Shake session with the ability to enqueue Actions for running. --   Keeps the 'ShakeDatabase' open, so at most one 'ShakeSession' per database.-data ShakeSession = ShakeSession-  { cancelShakeSession :: !(IO [DelayedActionInternal])-    -- ^ Closes the Shake session and returns the pending user actions-  , runInShakeSession  :: !(forall a . DelayedAction a -> IO (IO a))-    -- ^ Enqueue an action in the Shake session.+newtype ShakeSession = ShakeSession+  { cancelShakeSession :: IO ()+    -- ^ Closes the Shake session   }  -- | A Shake database plus persistent store. Can be thought of as storing@@ -358,6 +375,12 @@             -- (which would be an internal error).             evaluate (r `seqValue` Just r) +-- | Get all the files in the project+knownTargets :: Action (Hashed KnownTargets)+knownTargets = do+  ShakeExtras{knownTargetsVar} <- getShakeExtras+  liftIO $ readVar knownTargetsVar+ -- | Seq the result stored in the Shake value. This only -- evaluates the value to WHNF not NF. We take care of the latter -- elsewhere and doing it twice is expensive.@@ -393,6 +416,7 @@         hiddenDiagnostics <- newVar mempty         publishedDiagnostics <- newVar mempty         positionMapping <- newVar HMap.empty+        knownTargetsVar <- newVar $ hashed HMap.empty         let restartShakeSession = shakeRestart ideState         let session = shakeSession         mostRecentProgressEvent <- newTVarIO KickCompleted@@ -400,14 +424,17 @@         progressAsync <- async $             when reportProgress $                 progressThread mostRecentProgressEvent inProgress+        exportsMap <- newVar mempty +        actionQueue <- newQueue+         pure (ShakeExtras{..}, cancel progressAsync)     (shakeDbM, shakeClose) <-         shakeOpenDatabase             opts { shakeExtra = addShakeExtra shakeExtras $ shakeExtra opts }             rules     shakeDb <- shakeDbM-    initSession <- newSession shakeExtras shakeDb [] []+    initSession <- newSession shakeExtras shakeDb []     shakeSession <- newMVar initSession     let ideState = IdeState{..}     return ideState@@ -511,33 +538,21 @@   mkDelayedAction :: String -> Logger.Priority -> Action a -> DelayedAction a-mkDelayedAction = DelayedAction--data DelayedAction a = DelayedAction-  { actionName :: String -- ^ Name we use for debugging-  , actionPriority :: Logger.Priority -- ^ Priority with which to log the action-  , getAction :: Action a -- ^ The payload-  }--type DelayedActionInternal = DelayedAction ()--instance Show (DelayedAction a) where-    show d = "DelayedAction: " ++ actionName d+mkDelayedAction = DelayedAction Nothing  -- | These actions are run asynchronously after the current action is -- finished running. For example, to trigger a key build after a rule -- has already finished as is the case with useWithStaleFast delayedAction :: DelayedAction a -> IdeAction (IO a) delayedAction a = do-  sq <- asks session-  liftIO $ shakeEnqueueSession sq a+  extras <- ask+  liftIO $ shakeEnqueue extras a  -- | Restart the current 'ShakeSession' with the given system actions.---   Any computation running in the current session will be aborted,---   but user actions (added via 'shakeEnqueue') will be requeued.---   Progress is reported only on the system actions.-shakeRestart :: IdeState -> [DelayedAction a] -> IO ()-shakeRestart IdeState{..} systemActs =+--   Any actions running in the current session will be aborted,+--   but actions added via 'shakeEnqueue' will be requeued.+shakeRestart :: IdeState -> [DelayedAction ()] -> IO ()+shakeRestart IdeState{..} acts =     withMVar'         shakeSession         (\runner -> do@@ -554,86 +569,81 @@         -- It is crucial to be masked here, otherwise we can get killed         -- between spawning the new thread and updating shakeSession.         -- See https://github.com/digital-asset/ghcide/issues/79-        (\cancelled -> do-          (_b, dai) <- unzip <$> mapM instantiateDelayedAction systemActs-          (,()) <$> newSession shakeExtras shakeDb dai cancelled)+        (\() -> do+          (,()) <$> newSession shakeExtras shakeDb acts)  -- | Enqueue an action in the existing 'ShakeSession'. --   Returns a computation to block until the action is run, propagating exceptions. --   Assumes a 'ShakeSession' is available. -- --   Appropriate for user actions other than edits.-shakeEnqueue :: IdeState -> DelayedAction a -> IO (IO a)-shakeEnqueue IdeState{shakeSession} act = shakeEnqueueSession shakeSession act--shakeEnqueueSession :: MVar ShakeSession -> DelayedAction a -> IO (IO a)-shakeEnqueueSession sess act = withMVar sess $ \s -> runInShakeSession s act+shakeEnqueue :: ShakeExtras -> DelayedAction a -> IO (IO a)+shakeEnqueue ShakeExtras{actionQueue, logger} act = do+    (b, dai) <- instantiateDelayedAction act+    atomically $ pushQueue dai actionQueue+    let wait' b =+            waitBarrier b `catches`+              [ Handler(\BlockedIndefinitelyOnMVar ->+                    fail $ "internal bug: forever blocked on MVar for " <>+                            actionName act)+              , Handler (\e@AsyncCancelled -> do+                  logPriority logger Debug $ T.pack $ actionName act <> " was cancelled" --- | Set up a new 'ShakeSession' with a set of initial system and user actions--- Will crash if there is an existing 'ShakeSession' running.--- Progress is reported only on the system actions.--- Only user actions will get re-enqueued-newSession :: ShakeExtras -> ShakeDatabase -> [DelayedActionInternal] -> [DelayedActionInternal] -> IO ShakeSession-newSession ShakeExtras{..} shakeDb systemActs userActs = do-    -- A work queue for actions added via 'runInShakeSession'-    actionQueue :: TQueue DelayedActionInternal <- atomically $ do-        q <- newTQueue-        traverse_ (writeTQueue q) userActs-        return q-    actionInProgress :: TVar (Maybe DelayedActionInternal) <- newTVarIO Nothing+                  atomically $ abortQueue dai actionQueue+                  throw e)+              ]+    return (wait' b >>= either throwIO return) +-- | Set up a new 'ShakeSession' with a set of initial actions+--   Will crash if there is an existing 'ShakeSession' running.+newSession :: ShakeExtras -> ShakeDatabase -> [DelayedActionInternal] -> IO ShakeSession+newSession ShakeExtras{..} shakeDb acts = do+    reenqueued <- atomically $ peekInProgress actionQueue     let         -- A daemon-like action used to inject additional work         -- Runs actions from the work queue sequentially-        pumpAction =-            forever $ do-                join $ liftIO $ atomically $ do-                    act <- readTQueue actionQueue-                    writeTVar actionInProgress $ Just act-                    return (logDelayedAction logger act)-                liftIO $ atomically $ writeTVar actionInProgress Nothing+        pumpActionThread = do+            d <- liftIO $ atomically $ popQueue actionQueue+            void $ parallel [run d, pumpActionThread] +        run d  = do+            start <- liftIO offsetTime+            getAction d+            liftIO $ atomically $ doneQueue d actionQueue+            runTime <- liftIO start+            liftIO $ logPriority logger (actionPriority d) $ T.pack $+                "finish: " ++ actionName d ++ " (took " ++ showDuration runTime ++ ")"+         workRun restore = do-          let systemActs' = pumpAction : map getAction systemActs+          let acts' = pumpActionThread : map getAction (reenqueued ++ acts)           res <- try @SomeException-                 (restore $ shakeRunDatabase shakeDb systemActs')+                 (restore $ shakeRunDatabase shakeDb acts')           let res' = case res of                       Left e -> "exception: " <> displayException e                       Right _ -> "completed"-              -- Wrap up in a thread to avoid calling interruptible-              -- operations inside the masked section+           let wrapUp = logDebug logger $ T.pack $ "Finishing build session(" ++ res' ++ ")"           return wrapUp      -- Do the work in a background thread     workThread <- asyncWithUnmask workRun -    -- run the wrap up unmasked+    -- run the wrap up in a separate thread since it contains interruptible+    -- commands (and we are not using uninterruptible mask)     _ <- async $ join $ wait workThread --    -- 'runInShakeSession' is used to append work in this Shake session-    --  The session stays open until 'cancelShakeSession' is called-    let runInShakeSession :: forall a . DelayedAction a -> IO (IO a)-        runInShakeSession da = do-          (b, dai) <- instantiateDelayedAction da-          atomically $ writeTQueue actionQueue dai-          return (waitBarrier b >>= either throwIO return)-     --  Cancelling is required to flush the Shake database when either     --  the filesystem or the Ghc configuration have changed-        cancelShakeSession :: IO [DelayedActionInternal]-        cancelShakeSession = do-            cancel workThread-            atomically $ do-                q <- flushTQueue actionQueue-                c <- readTVar actionInProgress-                return (maybe [] pure c ++ q)+    let cancelShakeSession :: IO ()+        cancelShakeSession = cancel workThread      pure (ShakeSession{..}) -instantiateDelayedAction :: DelayedAction a -> IO (Barrier (Either SomeException a), DelayedActionInternal)-instantiateDelayedAction (DelayedAction s p a) = do+instantiateDelayedAction+    :: DelayedAction a+    -> IO (Barrier (Either SomeException a), DelayedActionInternal)+instantiateDelayedAction (DelayedAction _ s p a) = do+  u <- newUnique   b <- newBarrier   let a' = do         -- work gets reenqueued when the Shake session is restarted@@ -642,17 +652,10 @@         alreadyDone <- liftIO $ isJust <$> waitBarrierMaybe b         unless alreadyDone $ do           x <- actionCatch @SomeException (Right <$> a) (pure . Left)-          liftIO $ signalBarrier b x-  let d = DelayedAction s p a'-  return (b, d)--logDelayedAction :: Logger -> DelayedActionInternal -> Action ()-logDelayedAction l d  = do-    start <- liftIO offsetTime-    getAction d-    runTime <- liftIO start-    liftIO $ logPriority l (actionPriority d) $ T.pack $-        "finish: " ++ actionName d ++ " (took " ++ showDuration runTime ++ ")"+          liftIO $ do+            signalBarrier b x+      d' = DelayedAction (Just u) s p a'+  return (b, d')  getDiagnostics :: IdeState -> IO [FileDiagnostic] getDiagnostics IdeState{shakeExtras = ShakeExtras{diagnostics}} = do@@ -685,23 +688,30 @@                    mapMaybe (\((file, _key), v) -> (filePathToUri' file,) . Set.singleton <$> valueVersion v) $                    HMap.toList newState            modifyVar_ positionMapping $ \mappings -> return $! filterVersionMap versionsForFile mappings++-- | Define a new Rule without early cutoff define     :: IdeRule k v     => (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules () define op = defineEarlyCutoff $ \k v -> (Nothing,) <$> op k v +-- | Request a Rule result if available use :: IdeRule k v     => k -> NormalizedFilePath -> Action (Maybe v) use key file = head <$> uses key [file] +-- | Request a Rule result, it not available return the last computed result, if any, which may be stale useWithStale :: IdeRule k v     => k -> NormalizedFilePath -> Action (Maybe (v, PositionMapping)) useWithStale key file = head <$> usesWithStale key [file] +-- | Request a Rule result, it not available return the last computed result which may be stale.+--   Errors out if none available. useWithStale_ :: IdeRule k v     => k -> NormalizedFilePath -> Action (v, PositionMapping) useWithStale_ key file = head <$> usesWithStale_ key [file] +-- | Plural version of 'useWithStale_' usesWithStale_ :: IdeRule k v => k -> [NormalizedFilePath] -> Action [(v, PositionMapping)] usesWithStale_ key files = do     res <- usesWithStale key files@@ -818,7 +828,7 @@ type instance RuleResult (Q k) = A (RuleResult k)  --- | Return up2date results. Stale results will be ignored.+-- | Plural version of 'use' uses :: IdeRule k v     => k -> [NormalizedFilePath] -> Action [Maybe v] uses key files = map (\(A value) -> currentValue value) <$> apply (map (Q . (key,)) files)@@ -830,7 +840,7 @@     values <- map (\(A value) -> value) <$> apply (map (Q . (key,)) files)     zipWithM lastValue files values -+-- | Define a new Rule with early cutoff defineEarlyCutoff     :: IdeRule k v     => (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, IdeResult v))
src/Development/IDE/GHC/Compat.hs view
@@ -23,6 +23,7 @@     dontWriteHieFiles, #if !MIN_GHC_API_VERSION(8,8,0)     ml_hie_file,+    addBootSuffixLocnOut, #endif     hPutStringBuffer,     includePathsGlobal,@@ -30,6 +31,7 @@     addIncludePathsQuote,     getModuleHash,     getPackageName,+    setUpTypedHoles,     pattern DerivD,     pattern ForD,     pattern InstD,@@ -42,17 +44,20 @@     pattern IEThingWith,     pattern VarPat,     pattern PatSynBind,+    pattern ValBinds,+    pattern HsValBinds,     GHC.ModLocation,     Module.addBootSuffix,     pattern ModLocation,     getConArgs,-     HasSrcSpan,     getLoc,-     upNameCache,+    disableWarningsAsErrors,      module GHC,+    initializePlugins,+    applyPluginsParsedResultAction, #if MIN_GHC_API_VERSION(8,6,0)  #if MIN_GHC_API_VERSION(8,8,0)@@ -92,6 +97,8 @@       ModLocation,       HasSrcSpan,       PatSynBind,+      ValBinds,+      HsValBinds,       lookupName,       getLoc #if MIN_GHC_API_VERSION(8,6,0)@@ -100,12 +107,15 @@     ) import qualified HeaderInfo as Hdr import Avail+import Data.List (foldl') import ErrUtils (ErrorMessages) import FastString (FastString)  #if MIN_GHC_API_VERSION(8,6,0) import Development.IDE.GHC.HieAst (mkHieFile) import Development.IDE.GHC.HieBin+import qualified DynamicLoading+import Plugins (Plugin(parsedResultAction), withPlugins)  #if MIN_GHC_API_VERSION(8,8,0) import HieUtils@@ -119,9 +129,11 @@ #endif  #if !MIN_GHC_API_VERSION(8,8,0)+import qualified EnumSet  #if MIN_GHC_API_VERSION(8,6,0) import GhcPlugins (srcErrorMessages)+import Data.List (isSuffixOf) #else import System.IO.Error import IfaceEnv@@ -153,7 +165,9 @@  #if !MIN_GHC_API_VERSION(8,8,0) ml_hie_file :: GHC.ModLocation -> FilePath-ml_hie_file ml = ml_hi_file ml -<.> ".hie"+ml_hie_file ml+  | "boot" `isSuffixOf ` ml_hi_file ml = ml_hi_file ml -<.> ".hie-boot"+  | otherwise  = ml_hi_file ml -<.> ".hie" #endif  #endif@@ -284,6 +298,22 @@     GHC.PatSynBind x #endif +pattern ValBinds :: LHsBinds p -> [LSig p] -> HsValBindsLR p p+pattern ValBinds b s <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.ValBinds _ b s+#else+    GHC.ValBindsIn b s+#endif++pattern HsValBinds :: HsValBindsLR p p -> HsLocalBindsLR p p+pattern HsValBinds b <-+#if MIN_GHC_API_VERSION(8,6,0)+    GHC.HsValBinds _ b+#else+    GHC.HsValBinds b+#endif+ setHieDir :: FilePath -> DynFlags -> DynFlags setHieDir _f d = #if MIN_GHC_API_VERSION(8,8,0)@@ -300,6 +330,31 @@     d #endif +setUpTypedHoles ::DynFlags -> DynFlags+#if MIN_GHC_API_VERSION(8,6,0)+setUpTypedHoles df+  = flip gopt_unset Opt_AbstractRefHoleFits    -- too spammy+#if MIN_GHC_API_VERSION(8,8,0)+  $ flip gopt_unset Opt_ShowDocsOfHoleFits     -- not used+#endif+  $ flip gopt_unset Opt_ShowMatchesOfHoleFits  -- nice but broken (forgets module qualifiers)+  $ flip gopt_unset Opt_ShowProvOfHoleFits     -- not used+  $ flip gopt_unset Opt_ShowTypeAppOfHoleFits  -- not used+  $ flip gopt_unset Opt_ShowTypeAppVarsOfHoleFits -- not used+  $ flip gopt_unset Opt_ShowTypeOfHoleFits     -- massively simplifies parsing+  $ flip gopt_set   Opt_SortBySubsumHoleFits   -- very nice and fast enough in most cases+  $ flip gopt_unset Opt_SortValidHoleFits+  $ flip gopt_unset Opt_UnclutterValidHoleFits+  $ df+  { refLevelHoleFits = Just 1   -- becomes slow at higher levels+  , maxRefHoleFits   = Just 10  -- quantity does not impact speed+  , maxValidHoleFits = Nothing  -- quantity does not impact speed+  }+#else+setUpTypedHoles = id+#endif++ nameListFromAvails :: [AvailInfo] -> [(SrcSpan, Name)] nameListFromAvails as =   map (\n -> (nameSrcSpan n, n)) (concatMap availNames as)@@ -380,6 +435,14 @@ getHeaderImports a b c d =     catch (Right <$> Hdr.getImports a b c d)           (return . Left . srcErrorMessages)++-- | Add the @-boot@ suffix to all output file paths associated with the+-- module, not including the input file itself+addBootSuffixLocnOut :: GHC.ModLocation -> GHC.ModLocation+addBootSuffixLocnOut locn+  = locn { ml_hi_file  = Module.addBootSuffix (ml_hi_file locn)+         , ml_obj_file = Module.addBootSuffix (ml_obj_file locn)+         } #endif  getModuleHash :: ModIface -> Fingerprint@@ -398,3 +461,37 @@  getPackageName :: DynFlags -> Module.InstalledUnitId -> Maybe PackageName getPackageName dfs i = packageName <$> lookupPackage dfs (Module.DefiniteUnitId (Module.DefUnitId i))++disableWarningsAsErrors :: DynFlags -> DynFlags+disableWarningsAsErrors df =+    flip gopt_unset Opt_WarnIsError $ foldl' wopt_unset_fatal df [toEnum 0 ..]++#if !MIN_GHC_API_VERSION(8,8,0)+wopt_unset_fatal :: DynFlags -> WarningFlag -> DynFlags+wopt_unset_fatal dfs f+    = dfs { fatalWarningFlags = EnumSet.delete f (fatalWarningFlags dfs) }+#endif++#if MIN_GHC_API_VERSION(8,6,0)+initializePlugins :: HscEnv -> DynFlags -> IO DynFlags+initializePlugins env dflags = do+    DynamicLoading.initializePlugins env dflags++applyPluginsParsedResultAction :: HscEnv -> DynFlags -> ModSummary -> ApiAnns -> ParsedSource -> IO ParsedSource+applyPluginsParsedResultAction env dflags ms hpm_annotations parsed = do+  -- Apply parsedResultAction of plugins+  let applyPluginAction p opts = parsedResultAction p opts ms+  fmap hpm_module $ +    runHsc env $ withPlugins dflags applyPluginAction +      (HsParsedModule parsed [] hpm_annotations)++#else+initializePlugins :: HscEnv -> DynFlags -> IO DynFlags+initializePlugins _env dflags = do+    return dflags++applyPluginsParsedResultAction :: HscEnv -> DynFlags -> ModSummary -> ApiAnns -> ParsedSource -> IO ParsedSource+applyPluginsParsedResultAction _env _dflags _ms _hpm_annotations parsed =+    return parsed+#endif+
src/Development/IDE/GHC/Error.hs view
@@ -13,6 +13,7 @@   -- * utilities working with spans   , srcSpanToLocation   , srcSpanToRange+  , realSrcSpanToRange   , srcSpanToFilename   , zeroSpan   , realSpan@@ -25,6 +26,7 @@  import                     Development.IDE.Types.Diagnostics as D import qualified           Data.Text as T+import Data.Maybe import Development.IDE.Types.Location import Development.IDE.GHC.Orphans() import qualified FastString as FS@@ -41,9 +43,9 @@   diagFromText :: T.Text -> D.DiagnosticSeverity -> SrcSpan -> T.Text -> FileDiagnostic-diagFromText diagSource sev loc msg = (toNormalizedFilePath' $ srcSpanToFilename loc,ShowDiag,)+diagFromText diagSource sev loc msg = (toNormalizedFilePath' $ fromMaybe noFilePath $ srcSpanToFilename loc,ShowDiag,)     Diagnostic-    { _range    = srcSpanToRange loc+    { _range    = fromMaybe noRange $ srcSpanToRange loc     , _severity = Just sev     , _source   = Just diagSource -- not shown in the IDE, but useful for ghcide developers     , _message  = msg@@ -64,9 +66,9 @@ diagFromErrMsgs diagSource dflags = concatMap (diagFromErrMsg diagSource dflags) . bagToList  -- | Convert a GHC SrcSpan to a DAML compiler Range-srcSpanToRange :: SrcSpan -> Range-srcSpanToRange (UnhelpfulSpan _)  = noRange-srcSpanToRange (RealSrcSpan real) = realSrcSpanToRange real+srcSpanToRange :: SrcSpan -> Maybe Range+srcSpanToRange (UnhelpfulSpan _)  = Nothing+srcSpanToRange (RealSrcSpan real) = Just $ realSrcSpanToRange real  realSrcSpanToRange :: RealSrcSpan -> Range realSrcSpanToRange real =@@ -75,18 +77,21 @@  -- | Extract a file name from a GHC SrcSpan (use message for unhelpful ones) -- FIXME This may not be an _absolute_ file name, needs fixing.-srcSpanToFilename :: SrcSpan -> FilePath-srcSpanToFilename (UnhelpfulSpan fs) = FS.unpackFS fs-srcSpanToFilename (RealSrcSpan real) = FS.unpackFS $ srcSpanFile real+srcSpanToFilename :: SrcSpan -> Maybe FilePath+srcSpanToFilename (UnhelpfulSpan _) = Nothing+srcSpanToFilename (RealSrcSpan real) = Just $ FS.unpackFS $ srcSpanFile real -srcSpanToLocation :: SrcSpan -> Location-srcSpanToLocation src =+srcSpanToLocation :: SrcSpan -> Maybe Location+srcSpanToLocation src = do+  fs <- srcSpanToFilename src+  rng <- srcSpanToRange src   -- important that the URI's we produce have been properly normalized, otherwise they point at weird places in VS Code-  Location (fromNormalizedUri $ filePathToUri' $ toNormalizedFilePath' $ srcSpanToFilename src) (srcSpanToRange src)+  pure $ Location (fromNormalizedUri $ filePathToUri' $ toNormalizedFilePath' fs) rng  isInsideSrcSpan :: Position -> SrcSpan -> Bool-p `isInsideSrcSpan` r = sp <= p && p <= ep-  where Range sp ep = srcSpanToRange r+p `isInsideSrcSpan` r = case srcSpanToRange r of+  Just (Range sp ep) -> sp <= p && p <= ep+  _ -> False  -- | Convert a GHC severity to a DAML compiler Severity. Severities below -- "Warning" level are dropped (returning Nothing).
src/Development/IDE/GHC/Orphans.hs view
@@ -75,3 +75,8 @@ deriving instance Show SourceModified instance NFData SourceModified where     rnf = rwhnf++instance Show ModuleName where+    show = moduleNameString+instance Hashable ModuleName where+    hashWithSalt salt = hashWithSalt salt . show
src/Development/IDE/GHC/Util.hs view
@@ -6,6 +6,8 @@     -- * HcsEnv and environment     HscEnvEq,     hscEnv, newHscEnvEq,+    hscEnvWithImportPaths,+    envImportPaths,     modifyDynFlags,     evalGhcEnv,     runGhcEnv,@@ -28,7 +30,8 @@     hDuplicateTo',     setHieDir,     dontWriteHieFiles,-    ) where+    disableWarningsAsErrors,+    newHscEnvEqPreserveImportPaths) where  import Control.Concurrent import Data.List.Extra@@ -169,36 +172,58 @@  -- | An 'HscEnv' with equality. Two values are considered equal --   if they are created with the same call to 'newHscEnvEq'.-data HscEnvEq-    = HscEnvEq !Unique !HscEnv-               [(InstalledUnitId, DynFlags)] -- In memory components for this HscEnv+data HscEnvEq = HscEnvEq+    { envUnique :: !Unique+    , hscEnv :: !HscEnv+    , deps   :: [(InstalledUnitId, DynFlags)]+               -- ^ In memory components for this HscEnv                -- This is only used at the moment for the import dirs in                -- the DynFlags---- | Unwrap an 'HsEnvEq'.-hscEnv :: HscEnvEq -> HscEnv-hscEnv = either error id . hscEnv'+    , envImportPaths :: Maybe [String]+        -- ^ If Just, import dirs originally configured in this env+        --   If Nothing, the env import dirs are unaltered+    } -hscEnv' :: HscEnvEq -> Either String HscEnv-hscEnv' (HscEnvEq _ x _) = Right x-deps :: HscEnvEq -> [(InstalledUnitId, DynFlags)]-deps (HscEnvEq _ _ u) = u+-- | Wrap an 'HscEnv' into an 'HscEnvEq'.+newHscEnvEq :: FilePath -> HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq+newHscEnvEq cradlePath hscEnv0 deps = do+    envUnique <- newUnique+    let envImportPaths = Just $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0)+        relativeToCradle = (takeDirectory cradlePath </>)+        hscEnv = removeImportPaths hscEnv0+    return HscEnvEq{..}  -- | Wrap an 'HscEnv' into an 'HscEnvEq'.-newHscEnvEq :: HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq-newHscEnvEq e uids = do u <- newUnique; return $ HscEnvEq u e uids+newHscEnvEqPreserveImportPaths+    :: HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq+newHscEnvEqPreserveImportPaths hscEnv deps = do+    let envImportPaths = Nothing+    envUnique <- newUnique+    return HscEnvEq{..} +-- | Unwrap the 'HscEnv' with the original import paths.+--   Used only for locating imports+hscEnvWithImportPaths :: HscEnvEq -> HscEnv+hscEnvWithImportPaths HscEnvEq{..}+    | Just imps <- envImportPaths+    = hscEnv{hsc_dflags = (hsc_dflags hscEnv){importPaths = imps}}+    | otherwise+    = hscEnv++removeImportPaths :: HscEnv -> HscEnv+removeImportPaths hsc = hsc{hsc_dflags = (hsc_dflags hsc){importPaths = []}}+ instance Show HscEnvEq where-  show (HscEnvEq a _ _) = "HscEnvEq " ++ show (hashUnique a)+  show HscEnvEq{envUnique} = "HscEnvEq " ++ show (hashUnique envUnique)  instance Eq HscEnvEq where-  HscEnvEq a _ _ == HscEnvEq b _ _ = a == b+  a == b = envUnique a == envUnique b  instance NFData HscEnvEq where-  rnf (HscEnvEq a b c) = rnf (hashUnique a) `seq` b `seq` c `seq` ()+  rnf (HscEnvEq a b c d) = rnf (hashUnique a) `seq` b `seq` c `seq` rnf d  instance Hashable HscEnvEq where-  hashWithSalt s (HscEnvEq a _b _c) = hashWithSalt s a+  hashWithSalt s = hashWithSalt s . envUnique  -- Fake instance needed to persuade Shake to accept this type as a key. -- No harm done as ghcide never persists these keys currently
src/Development/IDE/Import/DependencyInformation.hs view
@@ -21,6 +21,7 @@   , reachableModules   , processDependencyInformation   , transitiveDeps+  , reverseDependencies    , BootIdMap   , insertBootId@@ -142,6 +143,8 @@     , depModuleDeps :: !(FilePathIdMap FilePathIdSet)     -- ^ For a non-error node, this contains the set of module immediate dependencies     -- in the same package.+    , depReverseModuleDeps :: !(IntMap IntSet)+    -- ^ Contains a reverse mapping from a module to all those that immediately depend on it.     , depPkgDeps :: !(FilePathIdMap (Set InstalledUnitId))     -- ^ For a non-error node, this contains the set of immediate pkg deps.     , depPathIdMap :: !PathIdMap@@ -222,6 +225,7 @@   DependencyInformation     { depErrorNodes = IntMap.fromList errorNodes     , depModuleDeps = moduleDeps+    , depReverseModuleDeps = reverseModuleDeps     , depModuleNames = IntMap.fromList $ coerce moduleNames     , depPkgDeps = pkgDependencies rawDepInfo     , depPathIdMap = rawPathIdMap@@ -232,16 +236,21 @@         moduleNames :: [(FilePathId, ModuleName)]         moduleNames =           [ (fId, modName) | (_, imports) <- successNodes, (L _ modName, fId) <- imports]-        successEdges :: [(FilePathId, FilePathId, [FilePathId])]+        successEdges :: [(FilePathId, [FilePathId])]         successEdges =             map-              (\(file, imports) -> (FilePathId file, FilePathId file, map snd imports))+              (bimap FilePathId (map snd))               successNodes         moduleDeps =           IntMap.fromList $-          map (\(_, FilePathId v, vs) -> (v, IntSet.fromList $ coerce vs))+          map (\(FilePathId v, vs) -> (v, IntSet.fromList $ coerce vs))             successEdges+        reverseModuleDeps =+          foldr (\(p, cs) res ->+                                  let new = IntMap.fromList (map (, IntSet.singleton (coerce p)) (coerce cs))+                                  in IntMap.unionWith IntSet.union new res ) IntMap.empty successEdges + -- | Given a dependency graph, buildResultGraph detects and propagates errors in that graph as follows: -- 1. Mark each node that is part of an import cycle as an error node. -- 2. Mark each node that has a parse error as an error node.@@ -306,6 +315,18 @@ partitionSCC (AcyclicSCC x:rest) = first (x:)   $ partitionSCC rest partitionSCC []                  = ([], []) +-- | Transitive reverse dependencies of a file+reverseDependencies :: NormalizedFilePath -> DependencyInformation -> [NormalizedFilePath]+reverseDependencies file DependencyInformation{..} =+  let FilePathId cur_id = pathToId depPathIdMap file+  in map (idToPath depPathIdMap . FilePathId) (IntSet.toList (go cur_id IntSet.empty))+  where+    go :: Int -> IntSet -> IntSet+    go k i =+      let outwards = fromMaybe IntSet.empty (IntMap.lookup k depReverseModuleDeps  )+          res = IntSet.union i outwards+          new = IntSet.difference i outwards+      in IntSet.foldr go res new  transitiveDeps :: DependencyInformation -> NormalizedFilePath -> Maybe TransitiveDependencies transitiveDeps DependencyInformation{..} file = do
src/Development/IDE/Import/FindImports.hs view
@@ -66,7 +66,7 @@ locateModuleFile :: MonadIO m              => [[FilePath]]              -> [String]-             -> (NormalizedFilePath -> m Bool)+             -> (ModuleName -> NormalizedFilePath -> m Bool)              -> Bool              -> ModuleName              -> m (Maybe NormalizedFilePath)@@ -74,7 +74,7 @@   let candidates import_dirs =         [ toNormalizedFilePath' (prefix </> M.moduleNameSlashes modName <.> maybeBoot ext)            | prefix <- import_dirs , ext <- exts]-  findM doesExist (concatMap candidates import_dirss)+  findM (doesExist modName) (concatMap candidates import_dirss)   where     maybeBoot ext       | isSource = ext ++ "-boot"@@ -92,12 +92,12 @@ locateModule     :: MonadIO m     => DynFlags-    -> [(M.InstalledUnitId, DynFlags)] -- Sets import directories to look in-    -> [String]-    -> (NormalizedFilePath -> m Bool)-    -> Located ModuleName-    -> Maybe FastString-    -> Bool+    -> [(M.InstalledUnitId, DynFlags)] -- ^ Import directories+    -> [String]                        -- ^ File extensions+    -> (ModuleName -> NormalizedFilePath -> m Bool)  -- ^ does file exist predicate+    -> Located ModuleName              -- ^ Moudle name+    -> Maybe FastString                -- ^ Package name+    -> Bool                            -- ^ Is boot module     -> m (Either [FileDiagnostic] Import) locateModule dflags comp_info exts doesExist modName mbPkgName isSource = do   case mbPkgName of
src/Development/IDE/LSP/LanguageServer.hs view
@@ -46,7 +46,7 @@     -> (InitializeRequest -> Either T.Text config)     -> (DidChangeConfigurationNotification -> Either T.Text config)     -> (IO LspId -> (FromServerMessage -> IO ()) -> VFSHandle -> ClientCapabilities-        -> WithProgressFunc -> WithIndefiniteProgressFunc -> IO IdeState)+        -> WithProgressFunc -> WithIndefiniteProgressFunc -> IO (Maybe config) -> Maybe FilePath -> IO IdeState)     -> IO () runLanguageServer options userHandlers onInitialConfig onConfigChange getIdeState = do     -- Move stdout to another file descriptor and duplicate stderr@@ -71,6 +71,8 @@     -- This should not happen but if it does, we will make sure that the whole server     -- dies and can be restarted instead of losing threads silently.     clientMsgBarrier <- newBarrier+    -- Forcefully exit+    let exit = signalBarrier clientMsgBarrier ()      -- The set of requests ids that we have received but not finished processing     pendingRequests <- newTVarIO Set.empty@@ -107,7 +109,8 @@             setHandlersOutline <>             userHandlers <>             setHandlersNotifications <> -- absolutely critical, join them with user notifications-            cancelHandler cancelRequest+            cancelHandler cancelRequest <>+            exitHandler exit             -- Cancel requests are special since they need to be handled             -- out of order to be useful. Existing handlers are run afterwards.     handlers <- parts WithMessage{withResponse, withNotification, withResponseAndRequest, withInitialize} def@@ -115,7 +118,7 @@     let initializeCallbacks = LSP.InitializeCallbacks             { LSP.onInitialConfiguration = onInitialConfig             , LSP.onConfigurationChange = onConfigChange-            , LSP.onStartup = handleInit (signalBarrier clientMsgBarrier ()) clearReqId waitForCancel clientMsgChan+            , LSP.onStartup = handleInit exit clearReqId waitForCancel clientMsgChan             }      void $ waitAnyCancel =<< traverse async@@ -133,10 +136,12 @@         handleInit exitClientMsg clearReqId waitForCancel clientMsgChan lspFuncs@LSP.LspFuncs{..} = do              ide <- getIdeState getNextReqId sendFunc (makeLSPVFSHandle lspFuncs) clientCapabilities-                               withProgress withIndefiniteProgress+                               withProgress withIndefiniteProgress config rootPath              _ <- flip forkFinally (const exitClientMsg) $ forever $ do                 msg <- readChan clientMsgChan+                -- We dispatch notifications synchronously and requests asynchronously+                -- This is to ensure that all file edits and config changes are applied before a request is handled                 case msg of                     Notification x@NotificationMessage{_params} act -> do                         catch (act lspFuncs ide _params) $ \(e :: SomeException) ->@@ -144,12 +149,12 @@                                 "Unexpected exception on notification, please report!\n" ++                                 "Message: " ++ show x ++ "\n" ++                                 "Exception: " ++ show e-                    Response x@RequestMessage{_id, _params} wrap act ->+                    Response x@RequestMessage{_id, _params} wrap act -> void $ async $                         checkCancelled ide clearReqId waitForCancel lspFuncs wrap act x _id _params $                             \case                               Left e  -> sendFunc $ wrap $ ResponseMessage "2.0" (responseId _id) (Left e)                               Right r -> sendFunc $ wrap $ ResponseMessage "2.0" (responseId _id) (Right r)-                    ResponseAndRequest x@RequestMessage{_id, _params} wrap wrapNewReq act ->+                    ResponseAndRequest x@RequestMessage{_id, _params} wrap wrapNewReq act -> void $ async $                         checkCancelled ide clearReqId waitForCancel lspFuncs wrap act x _id _params $                             \(res, newReq) -> do                                 case res of@@ -217,6 +222,9 @@             whenJust (LSP.cancelNotificationHandler x) ($ msg)     } +exitHandler :: IO () -> PartialHandlers c+exitHandler exit = PartialHandlers $ \_ x -> return x+    {LSP.exitNotificationHandler = Just $ const exit}  -- | A message that we need to deal with - the pieces are split up with existentials to gain additional type safety --   and defer precise processing until later (allows us to keep at a higher level of abstraction slightly longer)
src/Development/IDE/LSP/Notifications.hs view
@@ -15,16 +15,19 @@  import           Development.IDE.Core.IdeConfiguration import           Development.IDE.Core.Service+import           Development.IDE.Core.Shake import           Development.IDE.Types.Location import           Development.IDE.Types.Logger+import           Development.IDE.Types.Options  import           Control.Monad.Extra import           Data.Foldable                    as F import           Data.Maybe+import qualified Data.HashMap.Strict              as M import qualified Data.HashSet                     as S import qualified Data.Text                        as Text -import           Development.IDE.Core.FileStore   (setSomethingModified, setFileModified)+import           Development.IDE.Core.FileStore   (setSomethingModified, setFileModified, typecheckParents) import           Development.IDE.Core.FileExists  (modifyFileExists) import           Development.IDE.Core.OfInterest @@ -38,25 +41,34 @@         \_ ide (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> do             updatePositionMapping ide (VersionedTextDocumentIdentifier _uri (Just _version)) (List [])             whenUriFile _uri $ \file -> do-                modifyFilesOfInterest ide (S.insert file)-                setFileModified ide file+                -- We don't know if the file actually exists, or if the contents match those on disk+                -- For example, vscode restores previously unsaved contents on open+                modifyFilesOfInterest ide (M.insert file Modified)+                setFileModified ide False file                 logInfo (ideLogger ide) $ "Opened text document: " <> getUri _uri      ,LSP.didChangeTextDocumentNotificationHandler = withNotification (LSP.didChangeTextDocumentNotificationHandler x) $         \_ ide (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> do             updatePositionMapping ide identifier changes-            whenUriFile _uri $ \file -> setFileModified ide file+            whenUriFile _uri $ \file -> do+              modifyFilesOfInterest ide (M.insert file Modified)+              setFileModified ide False file             logInfo (ideLogger ide) $ "Modified text document: " <> getUri _uri      ,LSP.didSaveTextDocumentNotificationHandler = withNotification (LSP.didSaveTextDocumentNotificationHandler x) $         \_ ide (DidSaveTextDocumentParams TextDocumentIdentifier{_uri}) -> do-            whenUriFile _uri $ \file -> setFileModified ide file+            whenUriFile _uri $ \file -> do+                modifyFilesOfInterest ide (M.insert file OnDisk)+                setFileModified ide True file             logInfo (ideLogger ide) $ "Saved text document: " <> getUri _uri      ,LSP.didCloseTextDocumentNotificationHandler = withNotification (LSP.didCloseTextDocumentNotificationHandler x) $         \_ ide (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> do             whenUriFile _uri $ \file -> do-                modifyFilesOfInterest ide (S.delete file)+                modifyFilesOfInterest ide (M.delete file)+                -- Refresh all the files that depended on this+                IdeOptions{optCheckParents} <- getIdeOptionsIO $ shakeExtras ide+                when (optCheckParents >= CheckOnClose) $ typecheckParents ide file                 logInfo (ideLogger ide) $ "Closed text document: " <> getUri _uri     ,LSP.didChangeWatchedFilesNotificationHandler = withNotification (LSP.didChangeWatchedFilesNotificationHandler x) $         \_ ide (DidChangeWatchedFilesParams fileEvents) -> do
src/Development/IDE/LSP/Outline.hs view
@@ -22,7 +22,7 @@ import           Development.IDE.Core.Rules import           Development.IDE.Core.Shake import           Development.IDE.GHC.Compat-import           Development.IDE.GHC.Error      ( srcSpanToRange )+import           Development.IDE.GHC.Error      ( realSrcSpanToRange ) import           Development.IDE.LSP.Server import           Development.IDE.Types.Location import           Outputable                     ( Outputable@@ -46,12 +46,14 @@         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-                   }+               moduleSymbol = hsmodName >>= \case+                 (L (RealSrcSpan l) m) -> Just $+                   (defDocumentSymbol l :: DocumentSymbol)+                     { _name  = pprText m+                     , _kind  = SkFile+                     , _range = Range (Position 0 0) (Position maxBound 0) -- _ltop is 0 0 0 0+                     }+                 _ -> Nothing                importSymbols = maybe [] pure $                   documentSymbolForImportSummary                     (mapMaybe documentSymbolForImport hsmodImports)@@ -68,7 +70,7 @@     Nothing -> pure $ Right $ DSDocumentSymbols (List [])  documentSymbolForDecl :: Located (HsDecl GhcPs) -> Maybe DocumentSymbol-documentSymbolForDecl (L l (TyClD FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } }))+documentSymbolForDecl (L (RealSrcSpan l) (TyClD FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } }))   = Just (defDocumentSymbol l :: DocumentSymbol)     { _name   = showRdrName n                   <> (case pprText fdTyVars of@@ -78,7 +80,7 @@     , _detail = Just $ pprText fdInfo     , _kind   = SkClass     }-documentSymbolForDecl (L l (TyClD ClassDecl { tcdLName = L _ name, tcdSigs, tcdTyVars }))+documentSymbolForDecl (L (RealSrcSpan l) (TyClD ClassDecl { tcdLName = L _ name, tcdSigs, tcdTyVars }))   = Just (defDocumentSymbol l :: DocumentSymbol)     { _name     = showRdrName name                     <> (case pprText tcdTyVars of@@ -92,13 +94,13 @@         [ (defDocumentSymbol l :: DocumentSymbol)             { _name           = showRdrName n             , _kind           = SkMethod-            , _selectionRange = srcSpanToRange l'+            , _selectionRange = realSrcSpanToRange l'             }-        | L l  (ClassOpSig False names _) <- tcdSigs-        , L l' n                            <- names+        | L (RealSrcSpan l)  (ClassOpSig False names _) <- tcdSigs+        , L (RealSrcSpan l') n                            <- names         ]     }-documentSymbolForDecl (L l (TyClD DataDecl { tcdLName = L _ name, tcdDataDefn = HsDataDefn { dd_cons } }))+documentSymbolForDecl (L (RealSrcSpan l) (TyClD DataDecl { tcdLName = L _ name, tcdDataDefn = HsDataDefn { dd_cons } }))   = Just (defDocumentSymbol l :: DocumentSymbol)     { _name     = showRdrName name     , _kind     = SkStruct@@ -107,11 +109,11 @@         [ (defDocumentSymbol l :: DocumentSymbol)             { _name           = showRdrName n             , _kind           = SkConstructor-            , _selectionRange = srcSpanToRange l'+            , _selectionRange = realSrcSpanToRange l'             , _children       = conArgRecordFields (getConArgs x)             }-        | L l  x <- dd_cons-        , L l' n <- getConNames x+        | L (RealSrcSpan l ) x <- dd_cons+        , L (RealSrcSpan l') n <- getConNames x         ]     }   where@@ -122,48 +124,48 @@           , _kind = SkField           }       | L _ cdf <- lcdfs-      , L l n <- rdrNameFieldOcc . unLoc <$> cd_fld_names cdf+      , L (RealSrcSpan l) n <- rdrNameFieldOcc . unLoc <$> cd_fld_names cdf       ]     conArgRecordFields _ = Nothing-documentSymbolForDecl (L l (TyClD SynDecl { tcdLName = L l' n })) = Just+documentSymbolForDecl (L (RealSrcSpan l) (TyClD SynDecl { tcdLName = L (RealSrcSpan l') n })) = Just   (defDocumentSymbol l :: DocumentSymbol) { _name           = showRdrName n                                           , _kind           = SkTypeParameter-                                          , _selectionRange = srcSpanToRange l'+                                          , _selectionRange = realSrcSpanToRange l'                                           }-documentSymbolForDecl (L l (InstD ClsInstD { cid_inst = ClsInstDecl { cid_poly_ty } }))+documentSymbolForDecl (L (RealSrcSpan 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 } } }))+documentSymbolForDecl (L (RealSrcSpan 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 } } }))+documentSymbolForDecl (L (RealSrcSpan 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 })) =+documentSymbolForDecl (L (RealSrcSpan 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+documentSymbolForDecl (L (RealSrcSpan l) (ValD FunBind{fun_id = L _ name})) = Just     (defDocumentSymbol l :: DocumentSymbol)       { _name   = showRdrName name       , _kind   = SkFunction       }-documentSymbolForDecl (L l (ValD PatBind{pat_lhs})) = Just+documentSymbolForDecl (L (RealSrcSpan l) (ValD PatBind{pat_lhs})) = Just     (defDocumentSymbol l :: DocumentSymbol)       { _name   = pprText pat_lhs       , _kind   = SkFunction       } -documentSymbolForDecl (L l (ForD x)) = Just+documentSymbolForDecl (L (RealSrcSpan l) (ForD x)) = Just   (defDocumentSymbol l :: DocumentSymbol)     { _name   = case x of                   ForeignImport{} -> name@@ -203,7 +205,7 @@           }  documentSymbolForImport :: Located (ImportDecl GhcPs) -> Maybe DocumentSymbol-documentSymbolForImport (L l ImportDecl { ideclName, ideclQualified }) = Just+documentSymbolForImport (L (RealSrcSpan l) ImportDecl { ideclName, ideclQualified }) = Just   (defDocumentSymbol l :: DocumentSymbol)     { _name   = "import " <> pprText ideclName     , _kind   = SkModule@@ -213,18 +215,16 @@     , _detail = if ideclQualified then Just "qualified" else Nothing #endif     }-#if MIN_GHC_API_VERSION(8,6,0)-documentSymbolForImport (L _ XImportDecl {}) = Nothing-#endif+documentSymbolForImport _ = Nothing -defDocumentSymbol :: SrcSpan -> DocumentSymbol+defDocumentSymbol :: RealSrcSpan -> DocumentSymbol defDocumentSymbol l = DocumentSymbol { .. } where   _detail         = Nothing   _deprecated     = Nothing   _name           = ""   _kind           = SkUnknown 0-  _range          = srcSpanToRange l-  _selectionRange = srcSpanToRange l+  _range          = realSrcSpanToRange l+  _selectionRange = realSrcSpanToRange l   _children       = Nothing  showRdrName :: RdrName -> Text
src/Development/IDE/Plugin/CodeAction.hs view
@@ -14,7 +14,11 @@     , codeAction     , codeLens     , rulePackageExports-    , executeAddSignatureCommand+    , commandHandler++    -- * For testing+    , blockCommandId+    , typeSignatureCommandId     ) where  import Control.Monad (join, guard)@@ -30,6 +34,7 @@ import Development.IDE.Plugin.CodeAction.PositionIndexed import Development.IDE.Plugin.CodeAction.RuleTypes import Development.IDE.Plugin.CodeAction.Rules+import Development.IDE.Types.Exports import Development.IDE.Types.Location import Development.IDE.Types.Options import Development.Shake (Rules)@@ -46,10 +51,8 @@ import qualified Data.Text as T import Data.Tuple.Extra ((&&&)) import HscTypes-import SrcLoc (sortLocated) import Parser-import Text.Regex.TDFA ((=~), (=~~))-import Text.Regex.TDFA.Text()+import Text.Regex.TDFA (mrAfter, (=~), (=~~)) import Outputable (ppr, showSDocUnsafe) import DynFlags (xFlags, FlagSpec(..)) import GHC.LanguageExtensions.Type (Extension)@@ -58,6 +61,9 @@ import Data.Functor import Control.Applicative ((<|>)) import Safe (atMay)+import Bag (isEmptyBag)+import qualified Data.HashSet as Set+import Control.Concurrent.Extra (threadDelay, readVar)  plugin :: Plugin c plugin = codeActionPluginWithRules rules codeAction <> Plugin mempty setHandlersCodeLens@@ -65,6 +71,13 @@ rules :: Rules () rules = rulePackageExports +-- | a command that blocks forever. Used for testing+blockCommandId :: T.Text+blockCommandId = "ghcide.command.block"++typeSignatureCommandId :: T.Text+typeSignatureCommandId = "typesignature.add"+ -- | Generate code actions. codeAction     :: LSP.LspFuncs c@@ -83,10 +96,12 @@             <*> use GhcSession `traverse` mbFile     -- This is quite expensive 0.6-0.7s on GHC     pkgExports <- runAction "CodeAction:PackageExports" state $ (useNoFile_ . PackageExports) `traverse` env+    localExports <- readVar (exportsMap $ shakeExtras state)+    let exportsMap = localExports <> fromMaybe mempty pkgExports     let dflags = hsc_dflags . hscEnv <$> env     pure $ Right         [ CACodeAction $ CodeAction title (Just CodeActionQuickFix) (Just $ List [x]) (Just edit) Nothing-        | x <- xs, (title, tedit) <- suggestAction dflags (fromMaybe mempty pkgExports) ideOptions ( join parsedModule ) text x+        | x <- xs, (title, tedit) <- suggestAction dflags exportsMap ideOptions ( join parsedModule ) text x         , let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing         ] @@ -113,17 +128,23 @@       Nothing -> pure []  -- | Execute the "typesignature.add" command.-executeAddSignatureCommand+commandHandler     :: LSP.LspFuncs c     -> IdeState     -> ExecuteCommandParams     -> IO (Either ResponseError Value, Maybe (ServerMethod, ApplyWorkspaceEditParams))-executeAddSignatureCommand _lsp _ideState ExecuteCommandParams{..}+commandHandler lsp _ideState ExecuteCommandParams{..}     -- _command is prefixed with a process ID, because certain clients     -- have a global command registry, and all commands must be     -- unique. And there can be more than one ghcide instance running     -- at a time against the same client.-    | T.isSuffixOf "typesignature.add" _command+    | T.isSuffixOf blockCommandId _command+    = do+        LSP.sendFunc lsp $ NotCustomServer $+            NotificationMessage "2.0" (CustomServerMethod "ghcide/blocking/command") Null+        threadDelay maxBound+        return (Right Null, Nothing)+    | T.isSuffixOf typeSignatureCommandId _command     , Just (List [edit]) <- _arguments     , Success wedit <- fromJSON edit     = return (Right Null, Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams wedit))@@ -132,21 +153,21 @@  suggestAction   :: Maybe DynFlags-  -> PackageExportsMap+  -> ExportsMap   -> IdeOptions   -> Maybe ParsedModule   -> Maybe T.Text   -> Diagnostic   -> [(T.Text, [TextEdit])] suggestAction dflags packageExports ideOptions parsedModule text diag = concat-    [ suggestAddExtension diag+   -- Order these suggestions by priority+    [ suggestAddExtension diag             -- Highest priority+    , suggestSignature True diag     , suggestExtendImport dflags text diag-    , suggestFillHole diag     , suggestFillTypeWildcard diag     , suggestFixConstructorImport text diag     , suggestModuleTypo diag     , suggestReplaceIdentifier text diag-    , suggestSignature True diag     , suggestConstraint text diag     , removeRedundantConstraints text diag     , suggestAddTypeAnnotationToSatisfyContraints text diag@@ -154,16 +175,18 @@     [  suggestNewDefinition ideOptions pm text diag     ++ suggestRemoveRedundantImport pm text diag     ++ suggestNewImport packageExports pm diag-    ++ suggestDeleteTopBinding pm diag+    ++ suggestDeleteUnusedBinding pm text diag     ++ suggestExportUnusedTopBinding text pm diag-    | Just pm <- [parsedModule]]+    | Just pm <- [parsedModule]+    ] +++    suggestFillHole diag                   -- Lowest priority   suggestRemoveRedundantImport :: ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])] suggestRemoveRedundantImport ParsedModule{pm_parsed_source = L _  HsModule{hsmodImports}} contents Diagnostic{_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 [_, bindings] <- matchRegexUnifySpaces _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"+    , Just (L _ impDecl) <- find (\(L l _) -> srcSpanToRange l == Just _range ) hsmodImports     , Just c <- contents     , ranges <- map (rangesForBinding impDecl . T.unpack) (T.splitOn ", " bindings)     , ranges' <- extendAllToIncludeCommaIfPossible (indexedByPosition $ T.unpack c) (concat ranges)@@ -178,33 +201,121 @@         = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]     | otherwise = [] -suggestDeleteTopBinding :: ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])]-suggestDeleteTopBinding ParsedModule{pm_parsed_source = L _ HsModule{hsmodDecls}} Diagnostic{_range=_range,..}--- Foo.hs:4:1: warning: [-Wunused-top-binds] Defined but not used: ‘f’-    | Just [name] <- matchRegex _message ".*Defined but not used: ‘([^ ]+)’"-    , let allTopLevel = filter (isTopLevel . fst)-                        . map (\(L l b) -> (srcSpanToRange l, b))-                        . sortLocated-                        $ hsmodDecls-          sameName = filter (matchesBindingName (T.unpack name) . snd) allTopLevel-    , not (null sameName)-            = [("Delete ‘" <> name <> "’", flip TextEdit "" . toNextBinding allTopLevel . fst <$> sameName )]+suggestDeleteUnusedBinding :: ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]+suggestDeleteUnusedBinding+  ParsedModule{pm_parsed_source = L _ HsModule{hsmodDecls}}+  contents+  Diagnostic{_range=_range,..}+-- Foo.hs:4:1: warning: [-Wunused-binds] Defined but not used: ‘f’+    | Just [name] <- matchRegexUnifySpaces _message ".*Defined but not used: ‘([^ ]+)’"+    , Just indexedContent <- indexedByPosition . T.unpack <$> contents+      = let edits = flip TextEdit "" <$> relatedRanges indexedContent (T.unpack name)+        in ([("Delete ‘" <> name <> "’", edits) | not (null edits)])     | otherwise = []     where-      isTopLevel l = (_character . _start) l == 0+      relatedRanges indexedContent name =+        concatMap (findRelatedSpans indexedContent name) hsmodDecls+      toRange = realSrcSpanToRange+      extendForSpaces = extendToIncludePreviousNewlineIfPossible -      forwardLines lines r = r {_end = (_end r) {_line = (_line . _end $ r) + lines, _character = 0}}+      findRelatedSpans :: PositionIndexedString -> String -> Located (HsDecl GhcPs) -> [Range]+      findRelatedSpans+        indexedContent+        name+        (L (RealSrcSpan l) (ValD (extractNameAndMatchesFromFunBind -> Just (lname, matches)))) =+        case lname of+          (L nLoc _name) | isTheBinding nLoc ->+            let findSig (L (RealSrcSpan l) (SigD sig)) = findRelatedSigSpan indexedContent name l sig+                findSig _ = []+            in+              [extendForSpaces indexedContent $ toRange l]+              ++ concatMap findSig hsmodDecls+          _ -> concatMap (findRelatedSpanForMatch indexedContent name) matches+      findRelatedSpans _ _ _ = [] -      toNextBinding bindings r@Range { _end = Position {_line = l} }-        | Just (Range { _start = Position {_line = l'}}, _) <- find ((> l) . _line . _start . fst) bindings-        = forwardLines (l' - l) r-      toNextBinding _ r  = r+      extractNameAndMatchesFromFunBind+        :: HsBind GhcPs+        -> Maybe (Located (IdP GhcPs), [LMatch GhcPs (LHsExpr GhcPs)])+      extractNameAndMatchesFromFunBind+        FunBind+          { fun_id=lname+          , fun_matches=MG {mg_alts=L _ matches}+          } = Just (lname, matches)+      extractNameAndMatchesFromFunBind _ = Nothing -      matchesBindingName :: String -> HsDecl GhcPs -> Bool-      matchesBindingName b (ValD FunBind {fun_id=L _ x}) = showSDocUnsafe (ppr x) == b-      matchesBindingName b (SigD (TypeSig (L _ x:_) _)) = showSDocUnsafe (ppr x) == b-      matchesBindingName _ _ = False+      findRelatedSigSpan :: PositionIndexedString -> String -> RealSrcSpan -> Sig GhcPs -> [Range]+      findRelatedSigSpan indexedContent name l sig =+        let maybeSpan = findRelatedSigSpan1 name sig+        in case maybeSpan of+          Just (_span, True) -> pure $ extendForSpaces indexedContent $ toRange l -- a :: Int+          Just (RealSrcSpan span, False) -> pure $ toRange span -- a, b :: Int, a is unused+          _ -> [] +      -- Second of the tuple means there is only one match+      findRelatedSigSpan1 :: String -> Sig GhcPs -> Maybe (SrcSpan, Bool)+      findRelatedSigSpan1 name (TypeSig lnames _) =+        let maybeIdx = findIndex (\(L _ id) -> isSameName id name) lnames+        in case maybeIdx of+            Nothing -> Nothing+            Just _ | length lnames == 1 -> Just (getLoc $ head lnames, True)+            Just idx ->+              let targetLname = getLoc $ lnames !! idx+                  startLoc = srcSpanStart targetLname+                  endLoc = srcSpanEnd targetLname+                  startLoc' = if idx == 0+                              then startLoc+                              else srcSpanEnd . getLoc $ lnames !! (idx - 1)+                  endLoc' = if idx == 0 && idx < length lnames - 1+                            then srcSpanStart . getLoc $ lnames !! (idx + 1)+                            else endLoc+              in Just (mkSrcSpan startLoc' endLoc', False)+      findRelatedSigSpan1 _ _ = Nothing++      -- for where clause+      findRelatedSpanForMatch+        :: PositionIndexedString+        -> String+        -> LMatch GhcPs (LHsExpr GhcPs)+        -> [Range]+      findRelatedSpanForMatch+        indexedContent+        name+        (L _ Match{m_grhss=GRHSs{grhssLocalBinds}}) = do+        case grhssLocalBinds of+          (L _ (HsValBinds (ValBinds bag lsigs))) ->+            if isEmptyBag bag+            then []+            else concatMap (findRelatedSpanForHsBind indexedContent name lsigs) bag+          _ -> []+#if MIN_GHC_API_VERSION(8,6,0)+      findRelatedSpanForMatch _ _ _ = []+#endif++      findRelatedSpanForHsBind+        :: PositionIndexedString+        -> String+        -> [LSig GhcPs]+        -> LHsBind GhcPs+        -> [Range]+      findRelatedSpanForHsBind+        indexedContent+        name+        lsigs+        (L (RealSrcSpan l) (extractNameAndMatchesFromFunBind -> Just (lname, matches))) =+        if isTheBinding (getLoc lname)+        then+          let findSig (L (RealSrcSpan l) sig) = findRelatedSigSpan indexedContent name l sig+              findSig _ = []+          in [extendForSpaces indexedContent $ toRange l] ++ concatMap findSig lsigs+        else concatMap (findRelatedSpanForMatch indexedContent name) matches+      findRelatedSpanForHsBind _ _ _ _ = []++      isTheBinding :: SrcSpan -> Bool+      isTheBinding span = srcSpanToRange span == Just _range++      isSameName :: IdP GhcPs -> String -> Bool+      isSameName x name = showSDocUnsafe (ppr x) == name+ data ExportsAs = ExportName | ExportPattern | ExportAll   deriving (Eq) @@ -214,17 +325,17 @@ -- Foo.hs:5:1: warning: [-Wunused-top-binds] Defined but not used: type constructor or class ‘F’ -- Foo.hs:6:1: warning: [-Wunused-top-binds] Defined but not used: data constructor ‘Bar’   | Just source <- srcOpt-  , Just [name] <- matchRegex _message ".*Defined but not used: ‘([^ ]+)’"-                   <|> matchRegex _message ".*Defined but not used: type constructor or class ‘([^ ]+)’"-                   <|> matchRegex _message ".*Defined but not used: data constructor ‘([^ ]+)’"+  , Just [name] <- matchRegexUnifySpaces _message ".*Defined but not used: ‘([^ ]+)’"+                   <|> matchRegexUnifySpaces _message ".*Defined but not used: type constructor or class ‘([^ ]+)’"+                   <|> matchRegexUnifySpaces _message ".*Defined but not used: data constructor ‘([^ ]+)’"   , Just (exportType, _) <- find (matchWithDiagnostic _range . snd)                             . mapMaybe-                                (\(L l b) -> if isTopLevel $ srcSpanToRange l+                                (\(L l b) -> if maybe False isTopLevel $ srcSpanToRange l                                                 then exportsAs b else Nothing)                             $ hsmodDecls-  , Just pos <- _end . getLocatedRange <$> hsmodExports+  , Just pos <- fmap _end . getLocatedRange =<< hsmodExports   , Just needComma <- needsComma source <$> hsmodExports-  , let exportName = (if needComma then "," else "") <> printExport exportType name +  , let exportName = (if needComma then "," else "") <> printExport exportType name         insertPos = pos {_character = pred $ _character pos}   = [("Export ‘" <> name <> "’", [TextEdit (Range insertPos insertPos) exportName])]   | otherwise = []@@ -232,18 +343,21 @@     -- we get the last export and the closing bracket and check for comma in that range     needsComma :: T.Text -> Located [LIE GhcPs] -> Bool     needsComma _ (L _ []) = False-    needsComma source x@(L _ exports) =-      let closeParan = _end $ getLocatedRange x-          lastExport = _end . getLocatedRange $ last exports-      in not $ T.isInfixOf "," $ textInRange (Range lastExport closeParan) source+    needsComma source (L (RealSrcSpan l) exports) =+      let closeParan = _end $ realSrcSpanToRange l+          lastExport = fmap _end . getLocatedRange $ last exports+      in case lastExport of+        Just lastExport -> not $ T.isInfixOf "," $ textInRange (Range lastExport closeParan) source+        _ -> False+    needsComma _ _ = False -    getLocatedRange :: Located a -> Range+    getLocatedRange :: Located a -> Maybe Range     getLocatedRange = srcSpanToRange . getLoc      matchWithDiagnostic :: Range -> Located (IdP GhcPs) -> Bool     matchWithDiagnostic Range{_start=l,_end=r} x =-      let loc = _start . getLocatedRange $ x-       in loc >= l && loc <= r+      let loc = fmap _start . getLocatedRange $ x+       in loc >= Just l && loc <= Just r      printExport :: ExportsAs -> T.Text -> T.Text     printExport ExportName x = x@@ -291,11 +405,11 @@ --       In the expression: seq "test" seq "test" (traceShow "test") --       In an equation for ‘f’: --          f = seq "test" seq "test" (traceShow "test")-    | Just [ty, lit] <- matchRegex _message (pat False False True)-                        <|> matchRegex _message (pat False False False)+    | Just [ty, lit] <- matchRegexUnifySpaces _message (pat False False True)+                        <|> matchRegexUnifySpaces _message (pat False False False)             = codeEdit ty lit (makeAnnotatedLit ty lit)     | Just source <- sourceOpt-    , Just [ty, lit] <- matchRegex _message (pat True True False)+    , Just [ty, lit] <- matchRegexUnifySpaces _message (pat True True False)             = let lit' = makeAnnotatedLit ty lit;                   tir = textInRange _range source               in codeEdit ty lit (T.replace lit lit' tir)@@ -337,9 +451,9 @@ suggestNewDefinition ideOptions parsedModule contents Diagnostic{_message, _range} --     * Variable not in scope: --         suggestAcion :: Maybe T.Text -> Range -> Range-    | Just [name, typ] <- matchRegex message "Variable not in scope: ([^ ]+) :: ([^*•]+)"+    | Just [name, typ] <- matchRegexUnifySpaces message "Variable not in scope: ([^ ]+) :: ([^*•]+)"     = newDefinitionAction ideOptions parsedModule _range name typ-    | Just [name, typ] <- matchRegex message "Found hole: _([^ ]+) :: ([^*•]+) Or perhaps"+    | Just [name, typ] <- matchRegexUnifySpaces message "Found hole: _([^ ]+) :: ([^*•]+) Or perhaps"     , [(label, newDefinitionEdits)] <- newDefinitionAction ideOptions parsedModule _range name typ     = [(label, mkRenameEdit contents _range name : newDefinitionEdits)]     | otherwise = []@@ -349,8 +463,8 @@ newDefinitionAction :: IdeOptions -> ParsedModule -> Range -> T.Text -> T.Text -> [(T.Text, [TextEdit])] newDefinitionAction IdeOptions{..} parsedModule Range{_start} name typ     | Range _ lastLineP : _ <--      [ srcSpanToRange l-      | (L l _) <- hsmodDecls+      [ realSrcSpanToRange sp+      | (L l@(RealSrcSpan sp) _) <- hsmodDecls       , _start `isInsideSrcSpan` l]     , nextLineP <- Position{ _line = _line lastLineP + 1, _character = 0}     = [ ("Define " <> sig@@ -422,49 +536,87 @@  suggestFillHole :: Diagnostic -> [(T.Text, [TextEdit])] suggestFillHole Diagnostic{_range=_range,..}---  ...Development/IDE/LSP/CodeAction.hs:103:9: warning:---   * Found hole: _ :: Int -> String---   * In the expression: _---     In the expression: _ a---     In an equation for ‘foo’: foo a = _ a---   * Relevant bindings include---       a :: Int---         (bound at ...Development/IDE/LSP/CodeAction.hs:103:5)---       foo :: Int -> String---         (bound at ...Development/IDE/LSP/CodeAction.hs:103:1)---     Valid hole fits include---       foo :: Int -> String---         (bound at ...Development/IDE/LSP/CodeAction.hs:103:1)---       show :: forall a. Show a => a -> String---         with show @Int---         (imported from ‘Prelude’ at ...Development/IDE/LSP/CodeAction.hs:7:8-37---          (and originally defined in ‘GHC.Show’))---       mempty :: forall a. Monoid a => a---         with mempty @(Int -> String)---         (imported from ‘Prelude’ at ...Development/IDE/LSP/CodeAction.hs:7:8-37---          (and originally defined in ‘GHC.Base’)) (lsp-ui)+    | Just holeName <- extractHoleName _message+    , (holeFits, refFits) <- processHoleSuggestions (T.lines _message)+    = map (proposeHoleFit holeName False) holeFits+    ++ map (proposeHoleFit holeName True) refFits+    | otherwise = []+    where+      extractHoleName = fmap head . flip matchRegexUnifySpaces "Found hole: ([^ ]*)"+      proposeHoleFit holeName parenthise name =+          ( "replace " <> holeName <> " with " <> name+          , [TextEdit _range $ if parenthise then parens name else name])+      parens x = "(" <> x <> ")" -    | topOfHoleFitsMarker `T.isInfixOf` _message = let-      findSuggestedHoleFits :: T.Text -> [T.Text]-      findSuggestedHoleFits = extractFitNames . selectLinesWithFits . dropPreceding . T.lines-      proposeHoleFit name = ("replace hole `" <> holeName <>  "` with " <> name, [TextEdit _range name])-      holeName = T.strip $ last $ T.splitOn ":" $ head . T.splitOn "::" $ head $ filter ("Found hole" `T.isInfixOf`) $ T.lines _message-      dropPreceding       = dropWhile (not . (topOfHoleFitsMarker `T.isInfixOf`))-      selectLinesWithFits = filter ("::" `T.isInfixOf`)-      extractFitNames     = map (T.strip . head . T.splitOn " :: ")-      in map proposeHoleFit $ nubOrd $ findSuggestedHoleFits _message+processHoleSuggestions :: [T.Text] -> ([T.Text], [T.Text])+processHoleSuggestions mm = (holeSuggestions, refSuggestions)+{-+    • Found hole: _ :: LSP.Handlers -    | otherwise = []+      Valid hole fits include def+      Valid refinement hole fits include+        fromMaybe (_ :: LSP.Handlers) (_ :: Maybe LSP.Handlers)+        fromJust (_ :: Maybe LSP.Handlers)+        haskell-lsp-types-0.22.0.0:Language.Haskell.LSP.Types.Window.$sel:_value:ProgressParams (_ :: ProgressParams+                                                                                                        LSP.Handlers)+        T.foldl (_ :: LSP.Handlers -> Char -> LSP.Handlers)+                (_ :: LSP.Handlers)+                (_ :: T.Text)+        T.foldl' (_ :: LSP.Handlers -> Char -> LSP.Handlers)+                 (_ :: LSP.Handlers)+                 (_ :: T.Text)+-}+  where+    t = id @T.Text+    holeSuggestions = do+      -- get the text indented under Valid hole fits+      validHolesSection <-+        getIndentedGroupsBy (=~ t " *Valid (hole fits|substitutions) include") mm+      -- the Valid hole fits line can contain a hole fit+      holeFitLine <-+        mapHead+            (mrAfter . (=~ t " *Valid (hole fits|substitutions) include"))+            validHolesSection+      let holeFit = T.strip $ T.takeWhile (/= ':') holeFitLine+      guard (not $ T.null holeFit)+      return holeFit+    refSuggestions = do -- @[]+      -- get the text indented under Valid refinement hole fits+      refinementSection <-+        getIndentedGroupsBy (=~ t " *Valid refinement hole fits include") mm+      -- get the text for each hole fit+      holeFitLines <- getIndentedGroups (tail refinementSection)+      let holeFit = T.strip $ T.unwords holeFitLines+      guard $ not $ holeFit =~ t "Some refinement hole fits suppressed"+      return holeFit +    mapHead f (a:aa) = f a : aa+    mapHead _ [] = []++-- > getIndentedGroups [" H1", "  l1", "  l2", " H2", "  l3"] = [[" H1,", "  l1", "  l2"], [" H2", "  l3"]]+getIndentedGroups :: [T.Text] -> [[T.Text]]+getIndentedGroups [] = []+getIndentedGroups ll@(l:_) = getIndentedGroupsBy ((== indentation l) . indentation) ll+-- |+-- > getIndentedGroupsBy (" H" `isPrefixOf`) [" H1", "  l1", "  l2", " H2", "  l3"] = [[" H1", "  l1", "  l2"], [" H2", "  l3"]]+getIndentedGroupsBy :: (T.Text -> Bool) -> [T.Text] -> [[T.Text]]+getIndentedGroupsBy pred inp = case dropWhile (not.pred) inp of+    (l:ll) -> case span (\l' -> indentation l < indentation l') ll of+        (indented, rest) -> (l:indented) : getIndentedGroupsBy pred rest+    _ -> []++indentation :: T.Text -> Int+indentation = T.length . T.takeWhile isSpace+ suggestExtendImport :: Maybe DynFlags -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])] suggestExtendImport (Just dflags) contents Diagnostic{_range=_range,..}     | Just [binding, mod, srcspan] <--      matchRegex _message+      matchRegexUnifySpaces _message       "Perhaps you want to add ‘([^’]*)’ to the import list in the import of ‘([^’]*)’ *\\((.*)\\).$"     , Just c <- contents     , POk _ (L _ name) <- runParser dflags (T.unpack binding) parseIdentifier     = let range = case [ x | (x,"") <- readSrcSpan (T.unpack srcspan)] of-            [s] -> let x = srcSpanToRange s+            [s] -> let x = realSrcSpanToRange s                    in x{_end = (_end x){_character = succ (_character (_end x))}}             _ -> error "bug in srcspan parser"           importLine = textInRange range c@@ -481,7 +633,7 @@     -- or     -- import Data.Aeson.Types( Result(..) ) (lsp-ui)   | Just [constructor, typ] <--    matchRegex _message+    matchRegexUnifySpaces _message     "‘([^’]*)’ is a data constructor of ‘([^’]*)’ To import it use"   = let fixedImport = typ <> "(" <> constructor <> ")"     in [("Fix import of " <> fixedImport, [TextEdit _range fixedImport])]@@ -525,7 +677,7 @@       findMissingConstraint :: T.Text -> Maybe T.Text       findMissingConstraint t =         let regex = "(No instance for|Could not deduce) \\((.+)\\) arising from a use of"-         in matchRegex t regex <&> last+         in matchRegexUnifySpaces t regex <&> last  normalizeConstraints :: T.Text -> T.Text -> T.Text normalizeConstraints existingConstraints constraint =@@ -543,7 +695,7 @@ -- • In the expression: x == y --   In an equation for ‘==’: (Wrap x) == (Wrap y) = x == y --   In the instance declaration for ‘Eq (Wrap a)’-  | Just [instanceDeclaration] <- matchRegex _message "In the instance declaration for ‘([^`]*)’"+  | Just [instanceDeclaration] <- matchRegexUnifySpaces _message "In the instance declaration for ‘([^`]*)’"   = let instanceLine = contents           & T.splitOn ("instance " <> instanceDeclaration)           & head & T.lines & length@@ -562,7 +714,7 @@ --   In an equation for ‘==’: --       (Pair x x') == (Pair y y') = x == y && x' == y'   | Just [instanceLineStr, constraintFirstCharStr]-    <- matchRegex _message "bound by the instance declaration at .+:([0-9]+):([0-9]+)"+    <- matchRegexUnifySpaces _message "bound by the instance declaration at .+:([0-9]+):([0-9]+)"   = let existingConstraints = findExistingConstraints _message         newConstraints = normalizeConstraints existingConstraints missingConstraint         instanceLine = readPositionNumber instanceLineStr@@ -586,7 +738,7 @@         <> "` to the context of the instance declaration"  findTypeSignatureName :: T.Text -> Maybe T.Text-findTypeSignatureName t = matchRegex t "([^ ]+) :: " <&> head+findTypeSignatureName t = matchRegexUnifySpaces t "([^ ]+) :: " <&> head  findTypeSignatureLine :: T.Text -> T.Text -> Int findTypeSignatureLine contents typeSignatureName =@@ -632,7 +784,7 @@       findExistingConstraints :: T.Text -> Maybe T.Text       findExistingConstraints message =         if message =~ ("from the context:" :: String)-           then fmap (T.strip . head) $ matchRegex message "\\. ([^=]+)"+           then fmap (T.strip . head) $ matchRegexUnifySpaces message "\\. ([^=]+)"            else Nothing        buildNewConstraints :: T.Text -> Maybe T.Text -> T.Text@@ -687,7 +839,7 @@         & T.lines         & head         & T.strip-        & (`matchRegex` "Redundant constraints?: (.+)")+        & (`matchRegexUnifySpaces` "Redundant constraints?: (.+)")         <&> (head >>> parseConstraints)        -- If the type signature is not formatted as expected (arbitrary number of spaces,@@ -724,7 +876,7 @@  ------------------------------------------------------------------------------------------------- -suggestNewImport :: PackageExportsMap -> ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])]+suggestNewImport :: ExportsMap -> ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])] suggestNewImport packageExportsMap ParsedModule {pm_parsed_source = L _ HsModule {..}} Diagnostic{_message}   | msg <- unifySpaces _message   , Just name <- extractNotInScopeName msg@@ -736,29 +888,34 @@           RealSrcLoc s -> Just $ srcLocLine s           _ -> Nothing   , insertPos <- Position insertLine 0-  , extendImportSuggestions <- matchRegex msg+  , extendImportSuggestions <- matchRegexUnifySpaces msg     "Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"   = [(imp, [TextEdit (Range insertPos insertPos) (imp <> "\n")])-    | imp <- constructNewImportSuggestions packageExportsMap name extendImportSuggestions+    | imp <- sort $ constructNewImportSuggestions packageExportsMap name extendImportSuggestions     ] suggestNewImport _ _ _ = []  constructNewImportSuggestions-  :: PackageExportsMap -> NotInScope -> Maybe [T.Text] -> [T.Text]+  :: ExportsMap -> NotInScope -> Maybe [T.Text] -> [T.Text] constructNewImportSuggestions exportsMap thingMissing notTheseModules = nubOrd-  [ renderNewImport identInfo m-  | (identInfo, m) <- fromMaybe [] $ Map.lookup name exportsMap+  [ suggestion+  | (identInfo, m) <- maybe [] Set.toList $ Map.lookup name (getExportsMap exportsMap)   , canUseIdent thingMissing identInfo   , m `notElem` fromMaybe [] notTheseModules+  , suggestion <- renderNewImport identInfo m   ]  where   renderNewImport identInfo m-    | Just q <- qual = "import qualified " <> m <> " as " <> q-    | otherwise      = "import " <> m <> " (" <> importWhat identInfo <> ")"+    | Just q <- qual+    , asQ <- if q == m then "" else " as " <> q+    = ["import qualified " <> m <> asQ]+    | otherwise+    = ["import " <> m <> " (" <> importWhat identInfo <> ")"+      ,"import " <> m ]    (qual, name) = case T.splitOn "." (notInScope thingMissing) of     [n]      -> (Nothing, n)-    segments -> (Just (T.concat $ init segments), last segments)+    segments -> (Just (T.intercalate "." $ init segments), last segments)   importWhat IdentInfo {parent, rendered}     | Just p <- parent = p <> "(" <> rendered <> ")"     | otherwise        = rendered@@ -780,30 +937,23 @@  extractNotInScopeName :: T.Text -> Maybe NotInScope extractNotInScopeName x-  | Just [name] <- matchRegex x "Data constructor not in scope: ([^ ]+)"+  | Just [name] <- matchRegexUnifySpaces x "Data constructor not in scope: ([^ ]+)"   = Just $ NotInScopeDataConstructor name-  | Just [name] <- matchRegex x "Not in scope: data constructor [^‘]*‘([^’]*)’"+  | Just [name] <- matchRegexUnifySpaces x "Not in scope: data constructor [^‘]*‘([^’]*)’"   = Just $ NotInScopeDataConstructor name-  | Just [name] <- matchRegex x "ot in scope: type constructor or class [^‘]*‘([^’]*)’"+  | Just [name] <- matchRegexUnifySpaces x "ot in scope: type constructor or class [^‘]*‘([^’]*)’"   = Just $ NotInScopeTypeConstructorOrClass name-  | Just [name] <- matchRegex x "ot in scope: \\(([^‘ ]+)\\)"+  | Just [name] <- matchRegexUnifySpaces x "ot in scope: \\(([^‘ ]+)\\)"   = Just $ NotInScopeThing name-  | Just [name] <- matchRegex x "ot in scope: ([^‘ ]+)"+  | Just [name] <- matchRegexUnifySpaces x "ot in scope: ([^‘ ]+)"   = Just $ NotInScopeThing name-  | Just [name] <- matchRegex x "ot in scope:[^‘]*‘([^’]*)’"+  | Just [name] <- matchRegexUnifySpaces x "ot in scope:[^‘]*‘([^’]*)’"   = Just $ NotInScopeThing name   | otherwise   = Nothing  ------------------------------------------------------------------------------------------------- -topOfHoleFitsMarker :: T.Text-topOfHoleFitsMarker =-#if MIN_GHC_API_VERSION(8,6,0)-  "Valid hole fits include"-#else-  "Valid substitutions include"-#endif  mkRenameEdit :: Maybe T.Text -> Range -> T.Text -> TextEdit mkRenameEdit contents range name =@@ -876,7 +1026,7 @@ -- | 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+    concatMap (mapMaybe srcSpanToRange . rangesForBinding' b') lies   where     b' = wrapOperatorInParens (unqualify b) @@ -913,16 +1063,25 @@         $  "importLine does not have the expected structure: "         <> T.unpack importLine +-- | 'matchRegex' combined with 'unifySpaces'+matchRegexUnifySpaces :: T.Text -> T.Text -> Maybe [T.Text]+matchRegexUnifySpaces message = matchRegex (unifySpaces message)+ -- | Returns Just (the submatches) for the first capture, or Nothing. matchRegex :: T.Text -> T.Text -> Maybe [T.Text]-matchRegex message regex = case unifySpaces message =~~ regex of+matchRegex message regex = case message =~~ regex of     Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings     Nothing -> Nothing  setHandlersCodeLens :: PartialHandlers c setHandlersCodeLens = PartialHandlers $ \WithMessage{..} x -> return x{-    LSP.codeLensHandler = withResponse RspCodeLens codeLens,-    LSP.executeCommandHandler = withResponseAndRequest RspExecuteCommand ReqApplyWorkspaceEdit executeAddSignatureCommand+    LSP.codeLensHandler =+        withResponse RspCodeLens codeLens,+    LSP.executeCommandHandler =+        withResponseAndRequest+            RspExecuteCommand+            ReqApplyWorkspaceEdit+            commandHandler     }  filterNewlines :: T.Text -> T.Text
src/Development/IDE/Plugin/CodeAction/PositionIndexed.hs view
@@ -5,6 +5,7 @@   , indexedByPosition   , indexedByPositionStartingFrom   , extendAllToIncludeCommaIfPossible+  , extendToIncludePreviousNewlineIfPossible   , mergeRanges   ) where@@ -106,7 +107,25 @@     -- a, |b|, c ===> a, |b, |c     [ range { _end = end' }     | (_, ',') : rest <- [after']-    , let (end', _) : _ = dropWhile (isSpace . snd) rest+    , (end', _) : _ <- pure $ dropWhile (isSpace . snd) rest     ]   | otherwise   = [range]++extendToIncludePreviousNewlineIfPossible :: PositionIndexedString -> Range -> Range+extendToIncludePreviousNewlineIfPossible indexedString range+  | Just (before, _, _) <- unconsRange range indexedString+  , maybeFirstSpacePos <- lastSpacePos $ reverse before+  = case maybeFirstSpacePos of+      Nothing -> range+      Just pos -> range { _start = pos }+  | otherwise = range+  where+    lastSpacePos :: PositionIndexedString -> Maybe Position+    lastSpacePos [] = Nothing+    lastSpacePos ((pos, c):xs) =+      if not $ isSpace c+      then Nothing -- didn't find any space+      else case xs of+              (y:ys) | isSpace $ snd y -> lastSpacePos (y:ys)+              _ -> Just pos
src/Development/IDE/Plugin/CodeAction/RuleTypes.hs view
@@ -1,57 +1,20 @@ {-# LANGUAGE TypeFamilies #-} module Development.IDE.Plugin.CodeAction.RuleTypes-    (PackageExports(..), PackageExportsMap+    (PackageExports(..)     ,IdentInfo(..)-    ,mkIdentInfos     ) where -import Avail (AvailInfo(..)) import Data.Hashable (Hashable) import Control.DeepSeq (NFData) import Data.Binary (Binary)-import Data.Text (pack, Text) import Development.IDE.GHC.Util+import Development.IDE.Types.Exports import Development.Shake (RuleResult)-import Data.HashMap.Strict (HashMap) import Data.Typeable (Typeable) import GHC.Generics (Generic)-import Name-import FieldLabel (flSelector) -type Identifier = Text-type ModuleName = Text--data IdentInfo = IdentInfo-    { name :: !Identifier-    , rendered :: Text-    , parent :: !(Maybe Text)-    , isDatacon :: !Bool-    }-    deriving (Eq, Generic, Show)--instance NFData IdentInfo--mkIdentInfos :: AvailInfo -> [IdentInfo]-mkIdentInfos (Avail n) =-    [IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing (isDataConName n)]-mkIdentInfos (AvailTC parent (n:nn) flds)-    -- Following the GHC convention that parent == n if parent is exported-    | n == parent-    = [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) (Just $! parentP) True-        | n <- nn ++ map flSelector flds-      ] ++-      [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing False]-    where-        parentP = pack $ prettyPrint parent--mkIdentInfos (AvailTC _ nn flds)-    = [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing True-        | n <- nn ++ map flSelector flds-      ]- -- Rule type for caching Package Exports-type instance RuleResult PackageExports = PackageExportsMap-type PackageExportsMap = HashMap Identifier [(IdentInfo,ModuleName)]+type instance RuleResult PackageExports = ExportsMap  newtype PackageExports = PackageExports HscEnvEq     deriving (Eq, Show, Typeable, Generic)
src/Development/IDE/Plugin/CodeAction/Rules.hs view
@@ -3,26 +3,17 @@   ) where -import           Data.HashMap.Strict            ( fromListWith )-import           Data.Text                      ( Text-                                                , pack-                                                ) import           Data.Traversable               ( forM ) import           Development.IDE.Core.Rules import           Development.IDE.GHC.Util import           Development.IDE.Plugin.CodeAction.RuleTypes+import           Development.IDE.Types.Exports import           Development.Shake import           GHC                            ( DynFlags(pkgState) )-import           HscTypes                       ( IfaceExport-                                                , hsc_dflags-                                                , mi_exports-                                                )+import           HscTypes                       ( hsc_dflags) import           LoadIface import           Maybes-import           Module                         ( Module(..)-                                                , ModuleName-                                                , moduleNameString-                                                )+import           Module                         ( Module(..) ) import           Packages                       ( explicitPackages                                                 , exposedModules                                                 , packageConfigId@@ -43,19 +34,12 @@         , (mn, _)  <- exposedModules pkg         ] -  results <- forM targets $ \(pkg, mn) -> do+  modIfaces <- forM targets $ \(pkg, mn) -> do     modIface <- liftIO $ initIfaceLoad env $ loadInterface       ""       (Module (packageConfigId pkg) mn)       (ImportByUser False)-    case modIface of-      Failed    _err -> return mempty-      Succeeded mi   -> do-        let avails = mi_exports mi-        return $ concatMap (unpackAvail mn) avails-  return $ fromListWith (++) $ concat results--unpackAvail :: ModuleName -> IfaceExport -> [(Text, [(IdentInfo, Text)])]-unpackAvail mod =-  map (\id@IdentInfo {..} -> (name, [(id, pack $ moduleNameString mod)]))-    . mkIdentInfos+    return $ case modIface of+      Failed    _err -> Nothing+      Succeeded mi   -> Just mi+  return $ createExportsMap (catMaybes modIfaces)
src/Development/IDE/Plugin/Completions.hs view
@@ -2,7 +2,11 @@ {-# LANGUAGE TypeFamilies #-} #include "ghc-api-version.h" -module Development.IDE.Plugin.Completions(plugin) where+module Development.IDE.Plugin.Completions+    (+      plugin+    , getCompletionsLSP+    ) where  import Control.Applicative import Language.Haskell.LSP.Messages
src/Development/IDE/Plugin/Completions/Logic.hs view
@@ -15,6 +15,7 @@ import Data.List.Extra as List hiding (stripPrefix) import qualified Data.Map  as Map import Data.Maybe (fromMaybe, mapMaybe)+import qualified Data.Maybe as UnsafeMaybe (fromJust) import qualified Data.Text as T import qualified Text.Fuzzy as Fuzzy @@ -233,7 +234,7 @@       dflags = hsc_dflags packageState       curMod = ms_mod $ pm_mod_summary parsedMod       curModName = moduleName curMod-      Just (_,limports,_,_) = tm_renamed_source tm+      (_,limports,_,_) = UnsafeMaybe.fromJust $ tm_renamed_source tm -- safe because we always save the typechecked source        iDeclToModName :: ImportDecl name -> ModuleName       iDeclToModName = unLoc . ideclName
+ src/Development/IDE/Plugin/Test.hs view
@@ -0,0 +1,64 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+-- | A plugin that adds custom messages for use in tests+module Development.IDE.Plugin.Test (TestRequest(..), plugin) where++import Control.Monad.STM+import Data.Aeson+import Data.Aeson.Types+import Development.IDE.Core.Service+import Development.IDE.Core.Shake+import Development.IDE.GHC.Compat+import Development.IDE.GHC.Util (HscEnvEq(hscEnv))+import Development.IDE.LSP.Server+import Development.IDE.Plugin+import Development.IDE.Types.Action+import GHC.Generics (Generic)+import GhcPlugins (HscEnv(hsc_dflags))+import Language.Haskell.LSP.Core+import Language.Haskell.LSP.Messages+import Language.Haskell.LSP.Types+import System.Time.Extra+import Development.IDE.Core.RuleTypes++data TestRequest+    = BlockSeconds Seconds           -- ^ :: Null+    | GetInterfaceFilesDir FilePath  -- ^ :: String+    | GetShakeSessionQueueCount      -- ^ :: Number+    deriving Generic+    deriving anyclass (FromJSON, ToJSON)++plugin :: Plugin c+plugin = Plugin {+    pluginRules = return (),+    pluginHandler = PartialHandlers $ \WithMessage{..} x -> return x {+        customRequestHandler = withResponse RspCustomServer requestHandler'+    }+}+  where+      requestHandler' lsp ide req+        | Just customReq <- parseMaybe parseJSON req+        = requestHandler lsp ide customReq+        | otherwise+        = return $ Left+        $ ResponseError InvalidRequest "Cannot parse request" Nothing++requestHandler :: LspFuncs c+                -> IdeState+                -> TestRequest+                -> IO (Either ResponseError Value)+requestHandler lsp _ (BlockSeconds secs) = do+    sendFunc lsp $ NotCustomServer $+        NotificationMessage "2.0" (CustomServerMethod "ghcide/blocking/request") $+        toJSON secs+    sleep secs+    return (Right Null)+requestHandler _ s (GetInterfaceFilesDir fp) = do+    let nfp = toNormalizedFilePath fp+    sess <- runAction "Test - GhcSession" s $ use_ GhcSession nfp+    let hiPath = hiDir $ hsc_dflags $ hscEnv sess+    return $ Right (toJSON hiPath)+requestHandler _ s GetShakeSessionQueueCount = do+    n <- atomically $ countQueue $ actionQueue $ shakeExtras s+    return $ Right (toJSON n)+
src/Development/IDE/Spans/AtPoint.hs view
@@ -171,7 +171,7 @@   -> [SpanInfo]   -> MaybeT m [Location] querySpanInfoAt getSpan _ideOptions pos =-    lift . fmap (map srcSpanToLocation) . mapMaybeM getSpan . spansAtPoint pos+    lift . fmap (mapMaybe srcSpanToLocation) . mapMaybeM getSpan . spansAtPoint pos  -- | Given a 'Name' attempt to find the location where it is defined. nameToLocation :: Monad f => (Module -> MaybeT f (HieFile, String)) -> Name -> f (Maybe SrcSpan)
src/Development/IDE/Spans/Common.hs view
@@ -112,7 +112,7 @@ haddockToMarkdown (H.DocAppend d1 d2)   = haddockToMarkdown d1 ++ " " ++ haddockToMarkdown d2 haddockToMarkdown (H.DocString s)-  = s+  = escapeBackticks s haddockToMarkdown (H.DocParagraph p)   = "\n\n" ++ haddockToMarkdown p haddockToMarkdown (H.DocIdentifier i)@@ -120,7 +120,7 @@ haddockToMarkdown (H.DocIdentifierUnchecked i)   = "`" ++ i ++ "`" haddockToMarkdown (H.DocModule i)-  = "`" ++ i ++ "`"+  = "`" ++ escapeBackticks i ++ "`" haddockToMarkdown (H.DocWarning w)   = haddockToMarkdown w haddockToMarkdown (H.DocEmphasis d)@@ -128,11 +128,7 @@ 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+  = "`" ++ removeUnescapedBackticks (haddockToMarkdown d) ++ "`" haddockToMarkdown (H.DocCodeBlock d)   = "\n```haskell\n" ++ haddockToMarkdown d ++ "\n```\n" haddockToMarkdown (H.DocExamples es)@@ -149,7 +145,7 @@ haddockToMarkdown (H.DocPic (H.Picture url (Just label)))   = "![" ++ label ++ "](" ++ url ++ ")" haddockToMarkdown (H.DocAName aname)-  = "[" ++ aname ++ "]:"+  = "[" ++ escapeBackticks aname ++ "]:" haddockToMarkdown (H.DocHeader (H.Header level title))   = replicate level '#' ++ " " ++ haddockToMarkdown title @@ -173,6 +169,18 @@ -- things I don't really know how to handle haddockToMarkdown (H.DocProperty _)   = ""  -- don't really know what to do++escapeBackticks :: String -> String+escapeBackticks "" = ""+escapeBackticks ('`':ss) = '\\':'`':escapeBackticks ss+escapeBackticks (s  :ss) = s:escapeBackticks ss++removeUnescapedBackticks :: String -> String+removeUnescapedBackticks = \case+  '\\' : '`' : ss -> '\\' : '`' : removeUnescapedBackticks ss+  '`' : ss -> removeUnescapedBackticks ss+  "" -> ""+  s : ss -> s : removeUnescapedBackticks ss  splitForList :: String -> String splitForList s
src/Development/IDE/Spans/Documentation.hs view
@@ -31,6 +31,7 @@ import           GhcMonad import           Packages import           Name+import           Language.Haskell.LSP.Types (getUri, filePathToUri)  getDocumentationTryGhc :: GhcMonad m => Module -> [ParsedModule] -> Name -> m SpanDoc getDocumentationTryGhc mod deps n = head <$> getDocumentationsTryGhc mod deps [n]@@ -55,24 +56,25 @@ #endif     mkSpanDocText name =       pure (SpanDocText (getDocumentation sources name)) <*> getUris name-+        -- Get the uris to the documentation and source html pages if they exist     getUris name = do       df <- getSessionDynFlags-      (docFp, srcFp) <-+      (docFu, srcFu) <-         case nameModule_maybe name of           Just mod -> liftIO $ do-            doc <- fmap (fmap T.pack) $ lookupDocHtmlForModule df mod-            src <- fmap (fmap T.pack) $ lookupSrcHtmlForModule df mod+            doc <- toFileUriText $ lookupDocHtmlForModule df mod+            src <- toFileUriText $ lookupSrcHtmlForModule df mod             return (doc, src)           Nothing -> pure (Nothing, Nothing)-      let docUri = docFp >>= \fp -> pure $ "file://" <> fp <> "#" <> selector <> showName name-          srcUri = srcFp >>= \fp -> pure $ "file://" <> fp <> "#" <> showName name+      let docUri = (<> "#" <> selector <> showName name) <$> docFu+          srcUri = (<> "#" <> showName name) <$> srcFu           selector             | isValName name = "v:"             | otherwise = "t:"       return $ SpanDocUris docUri srcUri +    toFileUriText = (fmap . fmap) (getUri . filePathToUri)  getDocumentation  :: HasSrcSpan name
+ src/Development/IDE/Types/Action.hs view
@@ -0,0 +1,88 @@+module Development.IDE.Types.Action+  ( DelayedAction (..),+    DelayedActionInternal,+    ActionQueue,+    newQueue,+    pushQueue,+    popQueue,+    doneQueue,+    peekInProgress,+  abortQueue,countQueue)+where++import           Control.Concurrent.STM+import           Data.Hashable                (Hashable (..))+import           Data.HashSet                 (HashSet)+import qualified Data.HashSet                 as Set+import           Data.Unique                  (Unique)+import           Development.IDE.Types.Logger+import           Development.Shake            (Action)+import           Numeric.Natural++data DelayedAction a = DelayedAction+  { uniqueID       :: Maybe Unique,+    -- | Name we use for debugging+    actionName     :: String,+    -- | Priority with which to log the action+    actionPriority :: Priority,+    -- | The payload+    getAction      :: Action a+  }+  deriving (Functor)++type DelayedActionInternal = DelayedAction ()++instance Eq (DelayedAction a) where+  a == b = uniqueID a == uniqueID b++instance Hashable (DelayedAction a) where+  hashWithSalt s = hashWithSalt s . uniqueID++instance Show (DelayedAction a) where+  show d = "DelayedAction: " ++ actionName d++------------------------------------------------------------------------------++data ActionQueue = ActionQueue+  { newActions :: TQueue DelayedActionInternal,+    inProgress :: TVar (HashSet DelayedActionInternal)+  }++newQueue :: IO ActionQueue+newQueue = atomically $ do+  newActions <- newTQueue+  inProgress <- newTVar mempty+  return ActionQueue {..}++pushQueue :: DelayedActionInternal -> ActionQueue -> STM ()+pushQueue act ActionQueue {..} = writeTQueue newActions act++-- | You must call 'doneQueue' to signal completion+popQueue :: ActionQueue -> STM DelayedActionInternal+popQueue ActionQueue {..} = do+  x <- readTQueue newActions+  modifyTVar inProgress (Set.insert x)+  return x++-- | Completely remove an action from the queue+abortQueue :: DelayedActionInternal -> ActionQueue -> STM ()+abortQueue x ActionQueue {..} = do+  qq <- flushTQueue newActions+  mapM_ (writeTQueue newActions) (filter (/= x) qq)+  modifyTVar inProgress (Set.delete x)++-- | Mark an action as complete when called after 'popQueue'.+--   Has no effect otherwise+doneQueue :: DelayedActionInternal -> ActionQueue -> STM ()+doneQueue x ActionQueue {..} = do+  modifyTVar inProgress (Set.delete x)++countQueue :: ActionQueue -> STM Natural+countQueue ActionQueue{..} = do+    backlog <- flushTQueue newActions+    mapM_ (writeTQueue newActions) backlog+    m <- Set.size <$> readTVar inProgress+    return $ fromIntegral $ length backlog + m++peekInProgress :: ActionQueue -> STM [DelayedActionInternal]+peekInProgress ActionQueue {..} = Set.toList <$> readTVar inProgress
+ src/Development/IDE/Types/Exports.hs view
@@ -0,0 +1,75 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-}+module Development.IDE.Types.Exports+(+    IdentInfo(..),+    ExportsMap(..),+    createExportsMap,+) where++import Avail (AvailInfo(..))+import Control.DeepSeq (NFData)+import Data.Text (pack, Text)+import Development.IDE.GHC.Compat+import Development.IDE.GHC.Util+import Data.HashMap.Strict (HashMap)+import GHC.Generics (Generic)+import Name+import FieldLabel (flSelector)+import qualified Data.HashMap.Strict as Map+import GhcPlugins (IfaceExport)+import Data.HashSet (HashSet)+import qualified Data.HashSet as Set+import Data.Bifunctor (Bifunctor(second))+import Data.Hashable (Hashable)++newtype ExportsMap = ExportsMap+    {getExportsMap :: HashMap IdentifierText (HashSet (IdentInfo,ModuleNameText))}+    deriving newtype (Monoid, NFData, Show)++instance Semigroup ExportsMap where+    ExportsMap a <> ExportsMap b = ExportsMap $ Map.unionWith (<>) a b++type IdentifierText = Text+type ModuleNameText = Text++data IdentInfo = IdentInfo+    { name :: !Text+    , rendered :: Text+    , parent :: !(Maybe Text)+    , isDatacon :: !Bool+    }+    deriving (Eq, Generic, Show)+    deriving anyclass Hashable++instance NFData IdentInfo++mkIdentInfos :: AvailInfo -> [IdentInfo]+mkIdentInfos (Avail n) =+    [IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing (isDataConName n)]+mkIdentInfos (AvailTC parent (n:nn) flds)+    -- Following the GHC convention that parent == n if parent is exported+    | n == parent+    = [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) (Just $! parentP) True+        | n <- nn ++ map flSelector flds+      ] +++      [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing False]+    where+        parentP = pack $ prettyPrint parent++mkIdentInfos (AvailTC _ nn flds)+    = [ IdentInfo (pack (prettyPrint n)) (pack (printName n)) Nothing True+        | n <- nn ++ map flSelector flds+      ]++createExportsMap :: [ModIface] -> ExportsMap+createExportsMap = ExportsMap . Map.fromListWith (<>) . concatMap doOne+  where+    doOne mi = concatMap (fmap (second Set.fromList) . unpackAvail mn) (mi_exports mi)+      where+        mn = moduleName $ mi_module mi++unpackAvail :: ModuleName -> IfaceExport -> [(Text, [(IdentInfo, Text)])]+unpackAvail mod =+  map (\id@IdentInfo {..} -> (name, [(id, pack $ moduleNameString mod)]))+    . mkIdentInfos
src/Development/IDE/Types/Location.hs view
@@ -75,25 +75,25 @@ showPosition Position{..} = show (_line + 1) ++ ":" ++ show (_character + 1)  -- | Parser for the GHC output format-readSrcSpan :: ReadS SrcSpan+readSrcSpan :: ReadS RealSrcSpan readSrcSpan = readP_to_S (singleLineSrcSpanP <|> multiLineSrcSpanP)   where-    singleLineSrcSpanP, multiLineSrcSpanP :: ReadP SrcSpan+    singleLineSrcSpanP, multiLineSrcSpanP :: ReadP RealSrcSpan     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+      let from = mkRealSrcLoc fp l c0+          to   = mkRealSrcLoc fp l c1+      return $ mkRealSrcSpan from to      multiLineSrcSpanP = do       fp <- filePathP       s <- parensP (srcLocP fp)       void $ char '-'       e <- parensP (srcLocP fp)-      return $ mkSrcSpan s e+      return $ mkRealSrcSpan s e      parensP :: ReadP a -> ReadP a     parensP = between (char '(') (char ')')@@ -101,12 +101,12 @@     filePathP :: ReadP FastString     filePathP = fromString <$> (readFilePath <* char ':') <|> pure "" -    srcLocP :: FastString -> ReadP SrcLoc+    srcLocP :: FastString -> ReadP RealSrcLoc     srcLocP fp = do       l <- readS_to_P reads       void $ char ','       c <- readS_to_P reads-      return $ mkSrcLoc fp l c+      return $ mkRealSrcLoc fp l c      readFilePath :: ReadP FilePath     readFilePath = some ReadP.get
src/Development/IDE/Types/Options.hs view
@@ -2,7 +2,12 @@ -- SPDX-License-Identifier: Apache-2.0  {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DerivingStrategies #-} +{- HLINT ignore "Avoid restricted extensions" -}+ -- | Options module Development.IDE.Types.Options   ( IdeOptions(..)@@ -15,6 +20,11 @@   , defaultIdeOptions   , IdeResult   , IdeGhcSession(..)+  , LspConfig(..)+  , defaultLspConfig+  , CheckProject(..)+  , CheckParents(..)+  , OptHaddockParse(..)   ) where  import Development.Shake@@ -25,6 +35,8 @@ import qualified Data.Text as T import Development.IDE.Types.Diagnostics import Control.DeepSeq (NFData(..))+import Data.Aeson+import GHC.Generics  data IdeGhcSession = IdeGhcSession   { loadSessionFun :: FilePath -> IO (IdeResult HscEnvEq, [FilePath])@@ -73,8 +85,46 @@     --   features such as diagnostics and go-to-definition, in     --   situations in which they would become unavailable because of     --   the presence of type errors, holes or unbound variables.+  , optCheckProject :: CheckProject+    -- ^ Whether to typecheck the entire project on load+  , optCheckParents :: CheckParents+    -- ^ When to typecheck reverse dependencies of a file+  , optHaddockParse :: OptHaddockParse+    -- ^ Whether to return result of parsing module with Opt_Haddock.+    --   Otherwise, return the result of parsing without Opt_Haddock, so+    --   that the parsed module contains the result of Opt_KeepRawTokenStream,+    --   which might be necessary for hlint.+  , optCustomDynFlags :: DynFlags -> DynFlags+    -- ^ If given, it will be called right after setting up a new cradle,+    --   allowing to customize the Ghc options used   } +data OptHaddockParse = HaddockParse | NoHaddockParse+  deriving (Eq,Ord,Show,Enum)++newtype CheckProject = CheckProject { shouldCheckProject :: Bool }+  deriving stock (Eq, Ord, Show)+  deriving newtype (FromJSON,ToJSON)+data CheckParents+    -- Note that ordering of constructors is meaningful and must be monotonically+    -- increasing in the scenarios where parents are checked+    = NeverCheck+    | CheckOnClose+    | CheckOnSaveAndClose+    | AlwaysCheck+  deriving stock (Eq, Ord, Show, Generic)+  deriving anyclass (FromJSON, ToJSON)++data LspConfig+  = LspConfig+  { checkParents :: CheckParents+  , checkProject :: CheckProject+  } deriving stock (Eq, Ord, Show, Generic)+    deriving anyclass (FromJSON, ToJSON)++defaultLspConfig :: LspConfig+defaultLspConfig = LspConfig CheckOnSaveAndClose (CheckProject True)+ data IdePreprocessedSource = IdePreprocessedSource   { preprocWarnings :: [(GHC.SrcSpan, String)]     -- ^ Warnings emitted by the preprocessor.@@ -107,6 +157,10 @@     ,optKeywords = haskellKeywords     ,optDefer = IdeDefer True     ,optTesting = IdeTesting False+    ,optCheckProject = checkProject defaultLspConfig+    ,optCheckParents = checkParents defaultLspConfig+    ,optHaddockParse = HaddockParse+    ,optCustomDynFlags = id     }  
test/data/hover/GotoHover.hs view
@@ -1,6 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} {- HLINT ignore -}-module Testing ( module Testing ) where+module GotoHover ( module GotoHover) where import Data.Text (Text, pack) import Foo (Bar, foo) 
test/exe/Main.hs view
@@ -22,7 +22,7 @@ import Data.Maybe import Data.Rope.UTF16 (Rope) import qualified Data.Rope.UTF16 as Rope-import Development.IDE.Core.PositionMapping (fromCurrent, toCurrent)+import Development.IDE.Core.PositionMapping (fromCurrent, toCurrent, PositionResult(..), positionResultToMaybe) import Development.IDE.Core.Shake (Q(..)) import Development.IDE.GHC.Util import qualified Data.Text as T@@ -54,6 +54,9 @@ import Test.Tasty.Ingredients.Rerun import Test.Tasty.HUnit import Test.Tasty.QuickCheck+import System.Time.Extra+import Development.IDE.Plugin.CodeAction (typeSignatureCommandId, blockCommandId)+import Development.IDE.Plugin.Test (TestRequest(BlockSeconds,GetInterfaceFilesDir))  main :: IO () main = do@@ -74,7 +77,8 @@     , codeLensesTests     , outlineTests     , findDefinitionAndHoverTests-    , pluginTests+    , pluginSimpleTests+    , pluginParsedResultTests     , preprocessorTests     , thTests     , safeTests@@ -87,6 +91,9 @@     , nonLspCommandLine     , benchmarkTests     , ifaceTests+    , bootTests+    , rootUriTests+    , asyncTests     ]  initializeResponseTests :: TestTree@@ -124,7 +131,7 @@     , chk "NO doc link"               _documentLinkProvider  Nothing     , chk "NO color"                         _colorProvider (Just $ ColorOptionsStatic False)     , chk "NO folding range"          _foldingRangeProvider (Just $ FoldingRangeOptionsStatic False)-    , che "   execute command"      _executeCommandProvider (Just $ ExecuteCommandOptions $ List ["typesignature.add"])+    , che "   execute command"      _executeCommandProvider (Just $ ExecuteCommandOptions $ List [typeSignatureCommandId, blockCommandId])     , chk "   workspace"                         _workspace (Just $ WorkspaceOptions (Just WorkspaceFolderOptions{_supported = Just True, _changeNotifications = Just ( WorkspaceFolderChangeNotificationsBool True )}))     , chk "NO experimental"                   _experimental  Nothing     ] where@@ -468,6 +475,53 @@             Lens.filtered (T.isInfixOf ("/" <> name <> ".hs:"))           failure msg = liftIO $ assertFailure $ "Expected file path to be stripped but got " <> T.unpack msg       Lens.mapMOf_ offenders failure notification+  , testSession' "-Werror in cradle is ignored" $ \sessionDir -> do+      liftIO $ writeFile (sessionDir </> "hie.yaml")+        "cradle: {direct: {arguments: [\"-Wall\", \"-Werror\"]}}"+      let fooContent = T.unlines+            [ "module Foo where"+            , "foo = ()"+            ]+      _ <- createDoc "Foo.hs" "haskell" fooContent+      expectDiagnostics+        [ ( "Foo.hs"+          , [(DsWarning, (1, 0), "Top-level binding with no type signature:")+            ]+          )+        ]+  , testSessionWait "-Werror in pragma is ignored" $ do+      let fooContent = T.unlines+            [ "{-# OPTIONS_GHC -Wall -Werror #-}"+            , "module Foo() where"+            , "foo :: Int"+            , "foo = 1"+            ]+      _ <- createDoc "Foo.hs" "haskell" fooContent+      expectDiagnostics+        [ ( "Foo.hs"+          , [(DsWarning, (3, 0), "Defined but not used:")+            ]+          )+        ]+  , testCase "typecheck-all-parents-of-interest" $ withoutStackEnv $ runWithExtraFiles "recomp" $ \dir -> do+    let bPath = dir </> "B.hs"+        pPath = dir </> "P.hs"++    bSource <- liftIO $ readFileUtf8 bPath -- y :: Int+    pSource <- liftIO $ readFileUtf8 pPath -- bar = x :: Int++    bdoc <- createDoc bPath "haskell" bSource+    _pdoc <- createDoc pPath "haskell" pSource+    expectDiagnostics [("P.hs", [(DsWarning,(4,0), "Top-level binding")]) -- So that we know P has been loaded+                      ]++    -- Change y from Int to B which introduces a type error in A (imported from P)+    changeDoc bdoc [TextDocumentContentChangeEvent Nothing Nothing $+                    T.unlines ["module B where", "y :: Bool", "y = undefined"]]+    expectDiagnostics+      [("A.hs", [(DsError, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])+      ]+    expectNoMoreDiagnostics 2   ]  codeActionTests :: TestTree@@ -499,7 +553,7 @@ watchedFilesTests :: TestTree watchedFilesTests = testGroup "watched files"   [ testSession' "workspace files" $ \sessionDir -> do-      liftIO $ writeFile (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-isrc\"]}}"+      liftIO $ writeFile (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-isrc\", \"A\", \"WatchedFilesMissingModule\"]}}"       _doc <- createDoc "A.hs" "haskell" "{-#LANGUAGE NoImplicitPrelude #-}\nmodule A where\nimport WatchedFilesMissingModule"       watchedFileRegs <- getWatchedFilesSubscriptionsUntil @PublishDiagnosticsNotification @@ -512,7 +566,7 @@       liftIO $ length watchedFileRegs @?= 5    , testSession' "non workspace file" $ \sessionDir -> do-      liftIO $ writeFile (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-i/tmp\"]}}"+      liftIO $ writeFile (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-i/tmp\", \"A\", \"WatchedFilesMissingModule\"]}}"       _doc <- createDoc "A.hs" "haskell" "{-# LANGUAGE NoImplicitPrelude#-}\nmodule A where\nimport WatchedFilesMissingModule"       watchedFileRegs <- getWatchedFilesSubscriptionsUntil @PublishDiagnosticsNotification @@ -1006,10 +1060,15 @@     , test False []         "f = quickCheck"              []                "import Test.QuickCheck (quickCheck)"     ]   , testGroup "want suggestion"-    [ test True []          "f = nonEmpty"                []                "import Data.List.NonEmpty (nonEmpty)"+    [ wantWait  []          "f = foo"                     []                "import Foo (foo)"+    , wantWait  []          "f = Bar"                     []                "import Bar (Bar(Bar))"+    , wantWait  []          "f :: Bar"                    []                "import Bar (Bar)"+    , test True []          "f = nonEmpty"                []                "import Data.List.NonEmpty (nonEmpty)"     , test True []          "f = (:|)"                    []                "import Data.List.NonEmpty (NonEmpty((:|)))"     , test True []          "f :: Natural"                ["f = undefined"] "import Numeric.Natural (Natural)"+    , test True []          "f :: Natural"                ["f = undefined"] "import Numeric.Natural"     , test True []          "f :: NonEmpty ()"            ["f = () :| []"]  "import Data.List.NonEmpty (NonEmpty)"+    , test True []          "f :: NonEmpty ()"            ["f = () :| []"]  "import Data.List.NonEmpty"     , test True []          "f = First"                   []                "import Data.Monoid (First(First))"     , test True []          "f = Endo"                    []                "import Data.Monoid (Endo(Endo))"     , test True []          "f = Version"                 []                "import Data.Version (Version(Version))"@@ -1017,9 +1076,12 @@     , test True []          "f = AssertionFailed"         []                "import Control.Exception (AssertionFailed(AssertionFailed))"     , test True ["Prelude"] "f = nonEmpty"                []                "import Data.List.NonEmpty (nonEmpty)"     , test True []          "f :: Alternative f => f ()"  ["f = undefined"] "import Control.Applicative (Alternative)"+    , test True []          "f :: Alternative f => f ()"  ["f = undefined"] "import Control.Applicative"     , test True []          "f = empty"                   []                "import Control.Applicative (Alternative(empty))"+    , test True []          "f = empty"                   []                "import Control.Applicative"     , test True []          "f = (&)"                     []                "import Data.Function ((&))"     , test True []          "f = NE.nonEmpty"             []                "import qualified Data.List.NonEmpty as NE"+    , test True []          "f = Data.List.NonEmpty.nonEmpty" []            "import qualified Data.List.NonEmpty"     , test True []          "f :: Typeable a => a"        ["f = undefined"] "import Data.Typeable (Typeable)"     , test True []          "f = pack"                    []                "import Data.Text (pack)"     , test True []          "f :: Text"                   ["f = undefined"] "import Data.Text (Text)"@@ -1028,13 +1090,18 @@     ]   ]   where-    test wanted imps def other newImp = testSession' (T.unpack def) $ \dir -> do+    test = test' False+    wantWait = test' True True+    test' waitForCheckProject wanted imps def other newImp = testSessionWithExtraFiles "hover" (T.unpack def) $ \dir -> do       let before = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ def : other           after  = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ [newImp] ++ def : other-          cradle = "cradle: {direct: {arguments: [-hide-all-packages, -package, base, -package, text, -package-env, -]}}"+          cradle = "cradle: {direct: {arguments: [-hide-all-packages, -package, base, -package, text, -package-env, -, A, Bar, Foo, GotoHover]}}"       liftIO $ writeFileUTF8 (dir </> "hie.yaml") cradle       doc <- createDoc "Test.hs" "haskell" before+      void (skipManyTill anyMessage message :: Session WorkDoneProgressEndNotification)       _diags <- waitForDiagnostics+      -- there isn't a good way to wait until the whole project is checked atm+      when waitForCheckProject $ liftIO $ sleep 0.5       let defLine = length imps + 1           range = Range (Position defLine 0) (Position defLine maxBound)       actions <- getCodeActions doc range@@ -1201,6 +1268,91 @@         , ""         , "some = ()"       ])+  , testSession "delete unused binding in where clause" $+    testFor+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (h, g) where"+               , ""+               , "h :: Int"+               , "h = 3"+               , ""+               , "g :: Int"+               , "g = 6"+               , "  where"+               , "    h :: Int"+               , "    h = 4"+               , ""+               ])+    (10, 4)+    "Delete ‘h’"+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (h, g) where"+               , ""+               , "h :: Int"+               , "h = 3"+               , ""+               , "g :: Int"+               , "g = 6"+               , "  where"+               , ""+               ])+  , testSession "delete unused binding with multi-oneline signatures front" $+    testFor+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (b, c) where"+               , ""+               , "a, b, c :: Int"+               , "a = 3"+               , "b = 4"+               , "c = 5"+               ])+    (4, 0)+    "Delete ‘a’"+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (b, c) where"+               , ""+               , "b, c :: Int"+               , "b = 4"+               , "c = 5"+               ])+  , testSession "delete unused binding with multi-oneline signatures mid" $+    testFor+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (a, c) where"+               , ""+               , "a, b, c :: Int"+               , "a = 3"+               , "b = 4"+               , "c = 5"+               ])+    (5, 0)+    "Delete ‘b’"+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (a, c) where"+               , ""+               , "a, c :: Int"+               , "a = 3"+               , "c = 5"+               ])+  , testSession "delete unused binding with multi-oneline signatures end" $+    testFor+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (a, b) where"+               , ""+               , "a, b, c :: Int"+               , "a = 3"+               , "b = 4"+               , "c = 5"+               ])+    (6, 0)+    "Delete ‘c’"+    (T.unlines [ "{-# OPTIONS_GHC -Wunused-binds #-}"+               , "module A (a, b) where"+               , ""+               , "a, b :: Int"+               , "a = 3"+               , "b = 4"+               ])   ]   where     testFor source pos expectedTitle expectedResult = do@@ -1384,6 +1536,9 @@       , "bar :: Int -> Int -> String"       , "bar n parameterInt = " <> a <> " (n + " <> b <> " + " <> c <> ")  where"       , "  localConvert = (flip replicate) 'x'"+      , ""+      , "foo :: () -> Int -> String"+      , "foo = undefined"      ] @@ -1402,32 +1557,35 @@     liftIO $ expectedCode @=? modifiedCode   in   testGroup "fill typed holes"-  [ check "replace hole `_` with show"+  [ check "replace _ with show"           "_"    "n" "n"           "show" "n" "n" -  , check "replace hole `_` with globalConvert"+  , check "replace _ with globalConvert"           "_"             "n" "n"           "globalConvert" "n" "n"  #if MIN_GHC_API_VERSION(8,6,0)-  , check "replace hole `_convertme` with localConvert"+  , check "replace _convertme with localConvert"           "_convertme"   "n" "n"           "localConvert" "n" "n" #endif -  , check "replace hole `_b` with globalInt"+  , check "replace _b with globalInt"           "_a" "_b"        "_c"           "_a" "globalInt" "_c" -  , check "replace hole `_c` with globalInt"+  , check "replace _c with globalInt"           "_a" "_b"        "_c"           "_a" "_b" "globalInt"  #if MIN_GHC_API_VERSION(8,6,0)-  , check "replace hole `_c` with parameterInt"+  , check "replace _c with parameterInt"           "_a" "_b" "_c"           "_a" "_b"  "parameterInt"+  , check "replace _ with foo _"+          "_" "n" "n"+          "(foo _)" "n" "n" #endif   ] @@ -1672,8 +1830,8 @@         Nothing -- codeaction should not be available     , testSession "not top-level" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" -              , "{-# OPTIONS_GHC -Wunused-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"+              , "{-# OPTIONS_GHC -Wunused-binds #-}"               , "module A (foo,bar) where"               , "foo = ()"               , "  where bar = ()"@@ -1708,26 +1866,26 @@         (R 3 0 3 3)         "Export ‘foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A ("               , "foo) where"               , "foo = id"])     , testSession "single line explicit exports" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (foo) where"               , "foo = id"               , "bar = foo"])         (R 3 0 3 3)         "Export ‘bar’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (foo,bar) where"               , "foo = id"               , "bar = foo"])     , testSession "multi line explicit exports" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A"               , "  ("               , "    foo) where"@@ -1736,7 +1894,7 @@         (R 5 0 5 3)         "Export ‘bar’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A"               , "  ("               , "    foo,bar) where"@@ -1744,7 +1902,7 @@               , "bar = foo"])     , testSession "export list ends in comma" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A"               , "  (foo,"               , "  ) where"@@ -1753,7 +1911,7 @@         (R 4 0 4 3)         "Export ‘bar’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A"               , "  (foo,"               , "  bar) where"@@ -1761,83 +1919,83 @@               , "bar = foo"])     , testSession "unused pattern synonym" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" -              , "{-# LANGUAGE PatternSynonyms #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"+              , "{-# LANGUAGE PatternSynonyms #-}"               , "module A () where"               , "pattern Foo a <- (a, _)"])         (R 3 0 3 10)         "Export ‘Foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" -              , "{-# LANGUAGE PatternSynonyms #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"+              , "{-# LANGUAGE PatternSynonyms #-}"               , "module A (pattern Foo) where"               , "pattern Foo a <- (a, _)"])     , testSession "unused data type" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A () where"               , "data Foo = Foo"])         (R 2 0 2 7)         "Export ‘Foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (Foo(..)) where"               , "data Foo = Foo"])     , testSession "unused newtype" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A () where"               , "newtype Foo = Foo ()"])         (R 2 0 2 10)         "Export ‘Foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (Foo(..)) where"               , "newtype Foo = Foo ()"])     , testSession "unused type synonym" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A () where"               , "type Foo = ()"])         (R 2 0 2 7)         "Export ‘Foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (Foo) where"               , "type Foo = ()"])     , testSession "unused type family" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" -              , "{-# LANGUAGE TypeFamilies #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"+              , "{-# LANGUAGE TypeFamilies #-}"               , "module A () where"               , "type family Foo p"])         (R 3 0 3 15)         "Export ‘Foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" -              , "{-# LANGUAGE TypeFamilies #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"+              , "{-# LANGUAGE TypeFamilies #-}"               , "module A (Foo(..)) where"               , "type family Foo p"])     , testSession "unused typeclass" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A () where"               , "class Foo a"])         (R 2 0 2 8)         "Export ‘Foo’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (Foo(..)) where"               , "class Foo a"])     , testSession "infix" $ template         (T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A () where"               , "a `f` b = ()"])         (R 2 0 2 11)         "Export ‘f’"         (Just $ T.unlines-              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}" +              [ "{-# OPTIONS_GHC -Wunused-top-binds #-}"               , "module A (f) where"               , "a `f` b = ()"])     ]@@ -2032,7 +2190,7 @@   lstL43 = Position 47 12  ;  litL   = [ExpectHoverText ["[8391 :: Int, 6268]"]]   outL45 = Position 49  3  ;  outSig = [ExpectHoverText ["outer", "Bool"], mkR 46 0 46 5]   innL48 = Position 52  5  ;  innSig = [ExpectHoverText ["inner", "Char"], mkR 49 2 49 7]-  cccL17 = Position 17 11  ;  docLink = [ExpectHoverText ["[Documentation](file://"]]+  cccL17 = Position 17 11  ;  docLink = [ExpectHoverText ["[Documentation](file:///"]] #if MIN_GHC_API_VERSION(8,6,0)   imported = Position 56 13 ; importedSig = getDocUri "Foo.hs" >>= \foo -> return [ExpectHoverText ["foo", "Foo", "Haddock"], mkL foo 5 0 5 3]   reexported = Position 55 14 ; reexportedSig = getDocUri "Bar.hs" >>= \bar -> return [ExpectHoverText ["Bar", "Bar", "Haddock"], mkL bar 3 0 3 14]@@ -2067,7 +2225,7 @@   , 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     yes    docL41     doc           "documentation                     #7"   , test  no     yes    eitL40     kindE         "kind of Either                  #273"   , test  no     yes    intL40     kindI         "kind of Int                     #273"   , test  no     broken tvrL40     kindV         "kind of (* -> *) type variable  #273"@@ -2093,30 +2251,43 @@     void (openTestDataDoc (dir </> fp))     expectNoMoreDiagnostics 0.5 -+pluginSimpleTests :: TestTree+pluginSimpleTests = +  testSessionWait "simple plugin" $ do+    let content =+          T.unlines+            [ "{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}"+            , "{-# LANGUAGE DataKinds, ScopedTypeVariables, TypeOperators #-}"+            , "module Testing where"+            , "import Data.Proxy"+            , "import GHC.TypeLits"+            -- This function fails without plugins being initialized.+            , "f :: forall n. KnownNat n => Proxy n -> Integer"+            , "f _ = natVal (Proxy :: Proxy n) + natVal (Proxy :: Proxy (n+2))"+            , "foo :: Int -> Int -> Int"+            , "foo a b = a + c"+            ]+    _ <- createDoc "Testing.hs" "haskell" content+    expectDiagnostics+      [ ( "Testing.hs",+          [(DsError, (8, 14), "Variable not in scope: c")]+          )+      ] -pluginTests :: TestTree-pluginTests = (`xfail8101` "known broken (#556)")-            $ testSessionWait "plugins" $ do-  let content =-        T.unlines-          [ "{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}"-          , "{-# LANGUAGE DataKinds, ScopedTypeVariables, TypeOperators #-}"-          , "module Testing where"-          , "import Data.Proxy"-          , "import GHC.TypeLits"-          -- This function fails without plugins being initialized.-          , "f :: forall n. KnownNat n => Proxy n -> Integer"-          , "f _ = natVal (Proxy :: Proxy n) + natVal (Proxy :: Proxy (n+2))"-          , "foo :: Int -> Int -> Int"-          , "foo a b = a + c"-          ]-  _ <- createDoc "Testing.hs" "haskell" content-  expectDiagnostics-    [ ( "Testing.hs",-        [(DsError, (8, 14), "Variable not in scope: c")]-      )-    ]+pluginParsedResultTests :: TestTree +pluginParsedResultTests = +  (`xfail84` "record-dot-preprocessor unsupported on 8.4") $ testSessionWait "parsedResultAction plugin" $ do +    let content = +          T.unlines +            [ "{-# LANGUAGE DuplicateRecordFields, TypeApplications, FlexibleContexts, DataKinds, MultiParamTypeClasses, TypeSynonymInstances, FlexibleInstances #-}"+            , "{-# OPTIONS_GHC -fplugin=RecordDotPreprocessor #-}"+            , "module Testing (Company(..), display) where"+            , "data Company = Company {name :: String}"+            , "display :: Company -> String"+            , "display c = c.name"+            ]+    _ <- createDoc "Testing.hs" "haskell" content +    expectNoMoreDiagnostics 1  cppTests :: TestTree cppTests =@@ -2260,6 +2431,7 @@ -- | test that TH is reevaluated on typecheck thReloadingTest :: TestTree thReloadingTest = testCase "reloading-th-test" $ withoutStackEnv $ runWithExtraFiles "TH" $ \dir -> do+     let aPath = dir </> "THA.hs"         bPath = dir </> "THB.hs"         cPath = dir </> "THC.hs"@@ -2302,6 +2474,7 @@ completionTest :: String -> [T.Text] -> Position -> [(T.Text, CompletionItemKind, Bool, Bool)] -> TestTree completionTest name src pos expected = testSessionWait name $ do     docId <- createDoc "A.hs" "haskell" (T.unlines src)+    _ <- waitForDiagnostics     compls <- getCompletions docId pos     let compls' = [ (_label, _kind) | CompletionItem{..} <- compls]     liftIO $ do@@ -2576,11 +2749,11 @@ xfail :: TestTree -> String -> TestTree xfail = flip expectFailBecause -xfail8101 :: TestTree -> String -> TestTree-#if MIN_GHC_API_VERSION(8,10,0)-xfail8101 = flip expectFailBecause+xfail84 :: TestTree -> String -> TestTree+#if MIN_GHC_API_VERSION(8,6,0)+xfail84 t _ = t #else-xfail8101 t _ = t+xfail84 = flip expectFailBecause #endif  expectFailCabal :: String -> TestTree -> TestTree@@ -2666,6 +2839,7 @@ cradleTests :: TestTree cradleTests = testGroup "cradle"     [testGroup "dependencies" [sessionDepsArePickedUp]+    ,testGroup "ignore-fatal" [ignoreFatalWarning]     ,testGroup "loading" [loadCradleOnlyonce]     ,testGroup "multi"   [simpleMultiTest, simpleMultiTest2]     ]@@ -2755,6 +2929,13 @@         restore var Nothing = unsetEnv var         restore var (Just val) = setEnv var val True +ignoreFatalWarning :: TestTree+ignoreFatalWarning = testCase "ignore-fatal-warning" $ withoutStackEnv $ runWithExtraFiles "ignore-fatal" $ \dir -> do+    let srcPath = dir </> "IgnoreFatal.hs"+    src <- liftIO $ readFileUtf8 srcPath+    _ <- createDoc srcPath "haskell" src+    expectNoMoreDiagnostics 5+ simpleMultiTest :: TestTree simpleMultiTest = testCase "simple-multi-test" $ withoutStackEnv $ runWithExtraFiles "multi" $ \dir -> do     let aPath = dir </> "a/A.hs"@@ -2777,11 +2958,11 @@         bPath = dir </> "b/B.hs"     bSource <- liftIO $ readFileUtf8 bPath     bdoc <- createDoc bPath "haskell" bSource-    expectNoMoreDiagnostics 5+    expectNoMoreDiagnostics 10     aSource <- liftIO $ readFileUtf8 aPath     (TextDocumentIdentifier adoc) <- createDoc aPath "haskell" aSource     -- Need to have some delay here or the test fails-    expectNoMoreDiagnostics 6+    expectNoMoreDiagnostics 10     locs <- getDefinitions bdoc (Position 2 7)     let fooL = mkL adoc 2 0 2 3     checkDefs locs (pure [fooL])@@ -2796,6 +2977,22 @@     , ifaceTHTest     ] +bootTests :: TestTree+bootTests = testCase "boot-def-test" $ withoutStackEnv $ runWithExtraFiles "boot" $ \dir -> do+  let cPath = dir </> "C.hs"+  cSource <- liftIO $ readFileUtf8 cPath++  -- Dirty the cache+  liftIO $ runInDir dir $ do+    cDoc <- createDoc cPath "haskell" cSource+    _ <- getHover cDoc $ Position 4 3+    closeDoc cDoc++  cdoc <- createDoc cPath "haskell" cSource+  locs <- getDefinitions cdoc (Position 7 4)+  let floc = mkR 7 0 7 1+  checkDefs locs (pure [floc])+ -- | test that TH reevaluates across interfaces ifaceTHTest :: TestTree ifaceTHTest = testCase "iface-th-test" $ withoutStackEnv $ runWithExtraFiles "TH" $ \dir -> do@@ -2821,28 +3018,42 @@  ifaceErrorTest :: TestTree ifaceErrorTest = testCase "iface-error-test-1" $ withoutStackEnv $ runWithExtraFiles "recomp" $ \dir -> do-    let aPath = dir </> "A.hs"-        bPath = dir </> "B.hs"+    let bPath = dir </> "B.hs"         pPath = dir </> "P.hs" -    aSource <- liftIO $ readFileUtf8 aPath -- x = y :: Int     bSource <- liftIO $ readFileUtf8 bPath -- y :: Int     pSource <- liftIO $ readFileUtf8 pPath -- bar = x :: Int      bdoc <- createDoc bPath "haskell" bSource-    pdoc <- createDoc pPath "haskell" pSource     expectDiagnostics [("P.hs", [(DsWarning,(4,0), "Top-level binding")]) -- So what we know P has been loaded                       ]      -- Change y from Int to B     changeDoc bdoc [TextDocumentContentChangeEvent Nothing Nothing $ T.unlines ["module B where", "y :: Bool", "y = undefined"]]+    -- save so that we can that the error propogates to A+    sendNotification TextDocumentDidSave (DidSaveTextDocumentParams bdoc)      -- Check that the error propogates to A-    adoc <- createDoc aPath "haskell" aSource     expectDiagnostics       [("A.hs", [(DsError, (5, 4), "Couldn't match expected type 'Int' with actual type 'Bool'")])]-    closeDoc adoc -- Close A ++    -- Check that we wrote the interfaces for B when we saved+    lid <- sendRequest (CustomClientMethod "hidir") $ GetInterfaceFilesDir bPath+    res <- skipManyTill (message :: Session WorkDoneProgressCreateRequest) $+           skipManyTill (message :: Session WorkDoneProgressBeginNotification) $+             responseForId lid+    liftIO $ case res of+      ResponseMessage{_result=Right hidir} -> do+        hi_exists <- doesFileExist $ hidir </> "B.hi"+        assertBool ("Couldn't find B.hi in " ++ hidir) hi_exists+#if MIN_GHC_API_VERSION(8,6,0)+        hie_exists <- doesFileExist $ hidir </> "B.hie"+        assertBool ("Couldn't find B.hie in " ++ hidir) hie_exists+#endif+      _ -> assertFailure $ "Got malformed response for CustomMessage hidir: " ++ show res++    pdoc <- createDoc pPath "haskell" pSource     changeDoc pdoc [TextDocumentContentChangeEvent Nothing Nothing $ pSource <> "\nfoo = y :: Bool" ]     -- Now in P we have     -- bar = x :: Int@@ -2887,6 +3098,7 @@       ,("P.hs", [(DsWarning,(4,0), "Top-level binding")])       ,("P.hs", [(DsWarning,(6,0), "Top-level binding")])       ]+     expectNoMoreDiagnostics 2  ifaceErrorTest3 :: TestTree@@ -2980,9 +3192,51 @@         , Bench.name e /= "edit" -- the edit experiment does not ever fail     ] +-- | checks if we use InitializeParams.rootUri for loading session+rootUriTests :: TestTree+rootUriTests = testCase "use rootUri" . withoutStackEnv . runTest "dirA" "dirB" $ \dir -> do+  let bPath = dir </> "dirB/Foo.hs"+  liftIO $ copyTestDataFiles dir "rootUri"+  bSource <- liftIO $ readFileUtf8 bPath+  _ <- createDoc "Foo.hs" "haskell" bSource+  expectNoMoreDiagnostics 0.5+  where+    -- similar to run' except we can configure where to start ghcide and session+    runTest :: FilePath -> FilePath -> (FilePath -> Session ()) -> IO ()+    runTest dir1 dir2 s = withTempDir $ \dir -> runInDir' dir dir1 dir2 (s dir)++-- | Test if ghcide asynchronously handles Commands and user Requests+asyncTests :: TestTree+asyncTests = testGroup "async"+    [+      testSession "command" $ do+            -- Execute a command that will block forever+            let req = ExecuteCommandParams blockCommandId Nothing Nothing+            void $ sendRequest WorkspaceExecuteCommand req+            -- Load a file and check for code actions. Will only work if the command is run asynchronously+            doc <- createDoc "A.hs" "haskell" $ T.unlines+              [ "{-# OPTIONS -Wmissing-signatures #-}"+              , "foo = id"+              ]+            void waitForDiagnostics+            actions <- getCodeActions doc (Range (Position 1 0) (Position 1 0))+            liftIO $ [ _title | CACodeAction CodeAction{_title} <- actions] @=? ["add signature: foo :: a -> a"]+    , testSession "request" $ do+            -- Execute a custom request that will block for 1000 seconds+            void $ sendRequest (CustomClientMethod "test") $ BlockSeconds 1000+            -- Load a file and check for code actions. Will only work if the request is run asynchronously+            doc <- createDoc "A.hs" "haskell" $ T.unlines+              [ "{-# OPTIONS -Wmissing-signatures #-}"+              , "foo = id"+              ]+            void waitForDiagnostics+            actions <- getCodeActions doc (Range (Position 0 0) (Position 0 0))+            liftIO $ [ _title | CACodeAction CodeAction{_title} <- actions] @=? ["add signature: foo :: a -> a"]+    ]+ ---------------------------------------------------------------------- -- Utils-+----------------------------------------------------------------------  testSession :: String -> Session () -> TestTree testSession name = testCase name . run@@ -3022,7 +3276,7 @@ mkRange a b c d = Range (Position a b) (Position c d)  run :: Session a -> IO a-run s = withTempDir $ \dir -> runInDir dir s+run s = run' (const s)  runWithExtraFiles :: FilePath -> (FilePath -> Session a) -> IO a runWithExtraFiles prefix s = withTempDir $ \dir -> do@@ -3041,27 +3295,39 @@ run' s = withTempDir $ \dir -> runInDir dir (s dir)  runInDir :: FilePath -> Session a -> IO a-runInDir dir s = do+runInDir dir = runInDir' dir "." "."++-- | Takes a directory as well as relative paths to where we should launch the executable as well as the session root.+runInDir' :: FilePath -> FilePath -> FilePath -> Session a -> IO a+runInDir' dir startExeIn startSessionIn s = do   ghcideExe <- locateGhcideExecutable+  let startDir = dir </> startExeIn+  let projDir = dir </> startSessionIn +  createDirectoryIfMissing True startDir+  createDirectoryIfMissing True projDir   -- Temporarily hack around https://github.com/mpickering/hie-bios/pull/56   -- since the package import test creates "Data/List.hs", which otherwise has no physical home-  createDirectoryIfMissing True $ dir ++ "/Data"-+  createDirectoryIfMissing True $ projDir ++ "/Data" -  let cmd = unwords [ghcideExe, "--lsp", "--test", "--cwd", dir]+  let cmd = unwords [ghcideExe, "--lsp", "--test", "--verbose", "--cwd", startDir]   -- HIE calls getXgdDirectory which assumes that HOME is set.   -- Only sets HOME if it wasn't already set.   setEnv "HOME" "/homeless-shelter" False   let lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities $ Just True }-  runSessionWithConfig conf cmd lspTestCaps dir s+  logColor <- fromMaybe True <$> checkEnv "LSP_TEST_LOG_COLOR"+  runSessionWithConfig conf{logColor} cmd lspTestCaps projDir s   where+    checkEnv :: String -> IO (Maybe Bool)+    checkEnv s = fmap convertVal <$> getEnv s+    convertVal "0" = False+    convertVal _ = True+     conf = defaultConfig-      -- If you uncomment this you can see all logging-      -- which can be quite useful for debugging.-    --   { logStdErr = True, logColor = False }-      -- If you really want to, you can also see all messages-    --   { logMessages = True, logColor = False }+      -- uncomment this or set LSP_TEST_LOG_STDERR=1 to see all logging+    --   { logStdErr = True }+    --   uncomment this or set LSP_TEST_LOG_MESSAGES=1 to see all messages+    --   { logMessages = True }  openTestDataDoc :: FilePath -> Session TextDocumentIdentifier openTestDataDoc path = do@@ -3123,94 +3389,94 @@                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "ab"-                    (Position 0 0) @?= Just (Position 0 0)+                    (Position 0 0) @?= PositionExact (Position 0 0)               , testCase "after, same line, same length" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "ab"-                    (Position 0 3) @?= Just (Position 0 3)+                    (Position 0 3) @?= PositionExact (Position 0 3)               , testCase "after, same line, increased length" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc"-                    (Position 0 3) @?= Just (Position 0 4)+                    (Position 0 3) @?= PositionExact (Position 0 4)               , testCase "after, same line, decreased length" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "a"-                    (Position 0 3) @?= Just (Position 0 2)+                    (Position 0 3) @?= PositionExact (Position 0 2)               , testCase "after, next line, no newline" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc"-                    (Position 1 3) @?= Just (Position 1 3)+                    (Position 1 3) @?= PositionExact (Position 1 3)               , testCase "after, next line, newline" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc\ndef"-                    (Position 1 0) @?= Just (Position 2 0)+                    (Position 1 0) @?= PositionExact (Position 2 0)               , testCase "after, same line, newline" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc\nd"-                    (Position 0 4) @?= Just (Position 1 2)+                    (Position 0 4) @?= PositionExact (Position 1 2)               , testCase "after, same line, newline + newline at end" $                 toCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc\nd\n"-                    (Position 0 4) @?= Just (Position 2 1)+                    (Position 0 4) @?= PositionExact (Position 2 1)               , testCase "after, same line, newline + newline at end" $                 toCurrent                     (Range (Position 0 1) (Position 0 1))                     "abc"-                    (Position 0 1) @?= Just (Position 0 4)+                    (Position 0 1) @?= PositionExact (Position 0 4)               ]         , testGroup "fromCurrent"               [ testCase "before" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "ab"-                    (Position 0 0) @?= Just (Position 0 0)+                    (Position 0 0) @?= PositionExact (Position 0 0)               , testCase "after, same line, same length" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "ab"-                    (Position 0 3) @?= Just (Position 0 3)+                    (Position 0 3) @?= PositionExact (Position 0 3)               , testCase "after, same line, increased length" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc"-                    (Position 0 4) @?= Just (Position 0 3)+                    (Position 0 4) @?= PositionExact (Position 0 3)               , testCase "after, same line, decreased length" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "a"-                    (Position 0 2) @?= Just (Position 0 3)+                    (Position 0 2) @?= PositionExact (Position 0 3)               , testCase "after, next line, no newline" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc"-                    (Position 1 3) @?= Just (Position 1 3)+                    (Position 1 3) @?= PositionExact (Position 1 3)               , testCase "after, next line, newline" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc\ndef"-                    (Position 2 0) @?= Just (Position 1 0)+                    (Position 2 0) @?= PositionExact (Position 1 0)               , testCase "after, same line, newline" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc\nd"-                    (Position 1 2) @?= Just (Position 0 4)+                    (Position 1 2) @?= PositionExact (Position 0 4)               , testCase "after, same line, newline + newline at end" $                 fromCurrent                     (Range (Position 0 1) (Position 0 3))                     "abc\nd\n"-                    (Position 2 1) @?= Just (Position 0 4)+                    (Position 2 1) @?= PositionExact (Position 0 4)               , testCase "after, same line, newline + newline at end" $                 fromCurrent                     (Range (Position 0 1) (Position 0 1))                     "abc"-                    (Position 0 4) @?= Just (Position 0 1)+                    (Position 0 4) @?= PositionExact (Position 0 1)               ]         , adjustOption (\(QuickCheckTests i) -> QuickCheckTests (max 1000 i)) $ testGroup "properties"               [ testProperty "fromCurrent r t <=< toCurrent r t" $ do@@ -3226,9 +3492,9 @@                         pure (range, replacement, oldPos)                 forAll                     (suchThatMap gen-                        (\(range, replacement, oldPos) -> (range, replacement, oldPos,) <$> toCurrent range replacement oldPos)) $+                        (\(range, replacement, oldPos) -> positionResultToMaybe $ (range, replacement, oldPos,) <$> toCurrent range replacement oldPos)) $                     \(range, replacement, oldPos, newPos) ->-                    fromCurrent range replacement newPos === Just oldPos+                    fromCurrent range replacement newPos === PositionExact oldPos               , testProperty "toCurrent r t <=< fromCurrent r t" $ do                 let gen = do                         rope <- genRope@@ -3239,9 +3505,9 @@                         pure (range, replacement, newPos)                 forAll                     (suchThatMap gen-                        (\(range, replacement, newPos) -> (range, replacement, newPos,) <$> fromCurrent range replacement newPos)) $+                        (\(range, replacement, newPos) -> positionResultToMaybe $ (range, replacement, newPos,) <$> fromCurrent range replacement newPos)) $                     \(range, replacement, newPos, oldPos) ->-                    toCurrent range replacement oldPos === Just newPos+                    toCurrent range replacement oldPos === PositionExact newPos               ]         ] 
test/src/Development/IDE/Test.hs view
@@ -70,9 +70,9 @@             "Got unexpected diagnostics for " <> show fileUri <>             " got " <> show actual     handleCustomMethodResponse =-        -- the CustomClientMethod triggers a log message about ignoring it+        -- the CustomClientMethod triggers a RspCustomServer         -- handle that and then exit-        void (LspTest.message :: Session LogMessageNotification)+        void (LspTest.message :: Session CustomResponse)     ignoreOthers = void anyMessage >> handleMessages  expectDiagnostics :: [(FilePath, [(DiagnosticSeverity, Cursor, T.Text)])] -> Session ()