packages feed

ghcide 0.7.4.0 → 0.7.5.0

raw patch · 49 files changed

+1930/−1896 lines, 49 filesdep +dependent-mapdep +dependent-sumdep +lspdep −ghc-libdep −ghc-lib-parserdep −haskell-lspdep ~hls-plugin-apidep ~lsp-testPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: dependent-map, dependent-sum, lsp, lsp-types, unliftio, unliftio-core

Dependencies removed: ghc-lib, ghc-lib-parser, haskell-lsp, haskell-lsp-types

Dependency ranges changed: hls-plugin-api, lsp-test

API changes (from Hackage documentation)

- Development.IDE: [pluginHandler] :: Plugin c -> PartialHandlers c
- Development.IDE.Core.Rules: [transitiveNamedModuleDeps] :: TransitiveDependencies -> [NamedModuleDep]
- Development.IDE.Core.Rules: [transitivePkgDeps] :: TransitiveDependencies -> [InstalledUnitId]
- Development.IDE.Core.Rules: data TransitiveDependencies
- Development.IDE.Core.Shake: [eventer] :: ShakeExtras -> FromServerMessage -> IO ()
- Development.IDE.Core.Shake: [getLspId] :: ShakeExtras -> IO LspId
- Development.IDE.Core.Shake: [withIndefiniteProgress] :: ShakeExtras -> WithIndefiniteProgressFunc
- Development.IDE.Core.Shake: [withProgress] :: ShakeExtras -> WithProgressFunc
- Development.IDE.Core.Shake: sendEvent :: FromServerMessage -> Action ()
- Development.IDE.Import.DependencyInformation: [depPkgDeps] :: DependencyInformation -> !FilePathIdMap (Set InstalledUnitId)
- Development.IDE.Import.DependencyInformation: [packageImports] :: ModuleImports -> !Set InstalledUnitId
- Development.IDE.Import.DependencyInformation: [transitiveNamedModuleDeps] :: TransitiveDependencies -> [NamedModuleDep]
- Development.IDE.Import.DependencyInformation: [transitivePkgDeps] :: TransitiveDependencies -> [InstalledUnitId]
- Development.IDE.Import.DependencyInformation: data ModuleImports
- Development.IDE.Import.DependencyInformation: data TransitiveDependencies
- Development.IDE.LSP.HoverDefinition: setHandlersDefinition :: PartialHandlers c
- Development.IDE.LSP.HoverDefinition: setHandlersDocHighlight :: PartialHandlers c
- Development.IDE.LSP.HoverDefinition: setHandlersReferences :: PartialHandlers c
- Development.IDE.LSP.HoverDefinition: setHandlersTypeDefinition :: PartialHandlers c
- Development.IDE.LSP.HoverDefinition: setHandlersWsSymbols :: PartialHandlers c
- Development.IDE.LSP.Outline: setHandlersOutline :: PartialHandlers c
- Development.IDE.LSP.Protocol: pattern EventFileDiagnostics :: FilePath -> [Diagnostic] -> FromServerMessage
- Development.IDE.LSP.Server: PartialHandlers :: (WithMessage c -> Handlers -> IO Handlers) -> PartialHandlers c
- Development.IDE.LSP.Server: WithMessage :: (forall m req resp. (Show m, Show req, HasTracing req) => (ResponseMessage resp -> FromServerMessage) -> (LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp)) -> Maybe (Handler (RequestMessage m req resp))) -> (forall m req. (Show m, Show req, HasTracing req) => Maybe (Handler (NotificationMessage m req)) -> (LspFuncs c -> IdeState -> req -> IO ()) -> Maybe (Handler (NotificationMessage m req))) -> (forall m rm req resp newReqParams newReqBody. (Show m, Show rm, Show req, Show newReqParams, Show newReqBody, HasTracing req) => (ResponseMessage resp -> FromServerMessage) -> (RequestMessage rm newReqParams newReqBody -> FromServerMessage) -> (LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp, Maybe (rm, newReqParams))) -> Maybe (Handler (RequestMessage m req resp))) -> ((LspFuncs c -> IdeState -> InitializeParams -> IO ()) -> Maybe (Handler InitializeRequest)) -> WithMessage c
- Development.IDE.LSP.Server: [$sel:withInitialize:WithMessage] :: WithMessage c -> (LspFuncs c -> IdeState -> InitializeParams -> IO ()) -> Maybe (Handler InitializeRequest)
- Development.IDE.LSP.Server: [$sel:withNotification:WithMessage] :: WithMessage c -> forall m req. (Show m, Show req, HasTracing req) => Maybe (Handler (NotificationMessage m req)) -> (LspFuncs c -> IdeState -> req -> IO ()) -> Maybe (Handler (NotificationMessage m req))
- Development.IDE.LSP.Server: [$sel:withResponse:WithMessage] :: WithMessage c -> forall m req resp. (Show m, Show req, HasTracing req) => (ResponseMessage resp -> FromServerMessage) -> (LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp)) -> Maybe (Handler (RequestMessage m req resp))
- Development.IDE.LSP.Server: [$sel:withResponseAndRequest:WithMessage] :: WithMessage c -> forall m rm req resp newReqParams newReqBody. (Show m, Show rm, Show req, Show newReqParams, Show newReqBody, HasTracing req) => (ResponseMessage resp -> FromServerMessage) -> (RequestMessage rm newReqParams newReqBody -> FromServerMessage) -> (LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp, Maybe (rm, newReqParams))) -> Maybe (Handler (RequestMessage m req resp))
- Development.IDE.LSP.Server: class HasTracing a
- Development.IDE.LSP.Server: data WithMessage c
- Development.IDE.LSP.Server: instance (Language.Haskell.LSP.Types.Lens.HasTextDocument a doc, Language.Haskell.LSP.Types.Lens.HasUri doc Language.Haskell.LSP.Types.Uri.Uri) => Development.IDE.LSP.Server.HasTracing a
- Development.IDE.LSP.Server: instance Data.Default.Class.Default (Development.IDE.LSP.Server.PartialHandlers c)
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing (GHC.Maybe.Maybe Language.Haskell.LSP.Types.DataTypesJSON.InitializedParams)
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Data.Aeson.Types.Internal.Value
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Language.Haskell.LSP.Types.DataTypesJSON.DidChangeConfigurationParams
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Language.Haskell.LSP.Types.DataTypesJSON.DidChangeWatchedFilesParams
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Language.Haskell.LSP.Types.DataTypesJSON.ExecuteCommandParams
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Language.Haskell.LSP.Types.DataTypesJSON.InitializeParams
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Language.Haskell.LSP.Types.DataTypesJSON.WorkspaceSymbolParams
- Development.IDE.LSP.Server: instance Development.IDE.LSP.Server.HasTracing Language.Haskell.LSP.Types.WorkspaceFolders.DidChangeWorkspaceFoldersParams
- Development.IDE.LSP.Server: instance GHC.Base.Monoid (Development.IDE.LSP.Server.PartialHandlers c)
- Development.IDE.LSP.Server: instance GHC.Base.Semigroup (Development.IDE.LSP.Server.PartialHandlers c)
- Development.IDE.LSP.Server: newtype PartialHandlers c
- Development.IDE.LSP.Server: setUriAnd :: (HasTextDocument params a, HasUri a Uri) => (lspFuncs -> ide -> params -> IO res) -> lspFuncs -> SpanInFlight -> ide -> params -> IO res
- Development.IDE.LSP.Server: traceWithSpan :: HasTracing a => SpanInFlight -> a -> IO ()
- Development.IDE.Plugin: [pluginHandler] :: Plugin c -> PartialHandlers c
- Development.IDE.Plugin.HLS: instance Data.Aeson.Types.FromJSON.FromJSON Development.IDE.Plugin.HLS.FallbackCodeActionParams
- Development.IDE.Plugin.HLS: instance Data.Aeson.Types.ToJSON.ToJSON Development.IDE.Plugin.HLS.FallbackCodeActionParams
- Development.IDE.Plugin.HLS: instance GHC.Generics.Generic Development.IDE.Plugin.HLS.FallbackCodeActionParams
- Development.IDE.Session: defaultLoadingOptions :: SessionLoadingOptions
+ Development.IDE: [firstOpen] :: FileOfInterestStatus -> !Bool
+ Development.IDE: [pluginHandlers] :: Plugin c -> Handlers (ServerM c)
+ Development.IDE.Core.Compile: RunSimplifier :: Bool -> RunSimplifier
+ Development.IDE.Core.Compile: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> TcModuleResult
+ Development.IDE.Core.Compile: [tmrDeferedError] :: TcModuleResult -> !Bool
+ Development.IDE.Core.Compile: [tmrParsed] :: TcModuleResult -> ParsedModule
+ Development.IDE.Core.Compile: [tmrRenamed] :: TcModuleResult -> RenamedSource
+ Development.IDE.Core.Compile: [tmrTopLevelSplices] :: TcModuleResult -> Splices
+ Development.IDE.Core.Compile: [tmrTypechecked] :: TcModuleResult -> TcGblEnv
+ Development.IDE.Core.Compile: addRelativeImport :: NormalizedFilePath -> ModuleName -> DynFlags -> DynFlags
+ Development.IDE.Core.Compile: compileModule :: RunSimplifier -> HscEnv -> ModSummary -> TcGblEnv -> IO (IdeResult ModGuts)
+ Development.IDE.Core.Compile: computePackageDeps :: HscEnv -> InstalledUnitId -> IO (Either [FileDiagnostic] [InstalledUnitId])
+ Development.IDE.Core.Compile: data TcModuleResult
+ Development.IDE.Core.Compile: generateByteCode :: HscEnv -> ModSummary -> CgGuts -> IO (IdeResult Linkable)
+ Development.IDE.Core.Compile: generateHieAsts :: HscEnv -> TcModuleResult -> IO ([FileDiagnostic], Maybe (HieASTs Type))
+ Development.IDE.Core.Compile: generateObjectCode :: HscEnv -> ModSummary -> CgGuts -> IO (IdeResult Linkable)
+ Development.IDE.Core.Compile: getDocsBatch :: HscEnv -> Module -> [Name] -> IO [Either String (Maybe HsDocString, Map Int HsDocString)]
+ Development.IDE.Core.Compile: getModSummaryFromImports :: HscEnv -> FilePath -> UTCTime -> Maybe StringBuffer -> ExceptT [FileDiagnostic] IO (ModSummary, [LImportDecl GhcPs])
+ Development.IDE.Core.Compile: indexHieFile :: ShakeExtras -> ModSummary -> NormalizedFilePath -> Fingerprint -> HieFile -> IO ()
+ Development.IDE.Core.Compile: loadHieFile :: NameCacheUpdater -> FilePath -> IO HieFile
+ Development.IDE.Core.Compile: loadInterface :: MonadIO m => HscEnv -> ModSummary -> SourceModified -> Maybe LinkableType -> (Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult)) -> m ([FileDiagnostic], Maybe HiFileResult)
+ Development.IDE.Core.Compile: loadModulesHome :: [HomeModInfo] -> HscEnv -> HscEnv
+ Development.IDE.Core.Compile: lookupName :: HscEnv -> Module -> Name -> IO (Maybe TyThing)
+ Development.IDE.Core.Compile: mkHiFileResultCompile :: HscEnv -> TcModuleResult -> ModGuts -> LinkableType -> IO (IdeResult HiFileResult)
+ Development.IDE.Core.Compile: mkHiFileResultNoCompile :: HscEnv -> TcModuleResult -> IO HiFileResult
+ Development.IDE.Core.Compile: newtype RunSimplifier
+ Development.IDE.Core.Compile: parseModule :: IdeOptions -> HscEnv -> FilePath -> ModSummary -> IO (IdeResult ParsedModule)
+ Development.IDE.Core.Compile: setupFinderCache :: [ModSummary] -> HscEnv -> IO HscEnv
+ Development.IDE.Core.Compile: typecheckModule :: IdeDefer -> HscEnv -> [Linkable] -> ParsedModule -> IO (IdeResult TcModuleResult)
+ Development.IDE.Core.Compile: writeAndIndexHieFile :: HscEnv -> ShakeExtras -> ModSummary -> NormalizedFilePath -> [AvailInfo] -> HieASTs Type -> ByteString -> IO [FileDiagnostic]
+ Development.IDE.Core.Compile: writeHiFile :: HscEnv -> HiFileResult -> IO [FileDiagnostic]
+ Development.IDE.Core.OfInterest: [firstOpen] :: FileOfInterestStatus -> !Bool
+ Development.IDE.Core.RuleTypes: [firstOpen] :: FileOfInterestStatus -> !Bool
+ Development.IDE.Core.Rules: getParsedModuleDefinition :: HscEnv -> IdeOptions -> NormalizedFilePath -> ModSummary -> IO (Maybe ByteString, ([FileDiagnostic], Maybe ParsedModule))
+ Development.IDE.Core.Rules: ghcSessionDepsDefinition :: NormalizedFilePath -> Action (IdeResult HscEnvEq)
+ Development.IDE.Core.Rules: newtype TransitiveDependencies
+ Development.IDE.Core.Rules: regenerateHiFile :: HscEnvEq -> NormalizedFilePath -> ModSummary -> Maybe LinkableType -> Action ([FileDiagnostic], Maybe HiFileResult)
+ Development.IDE.Core.Rules: typeCheckRuleDefinition :: HscEnv -> ParsedModule -> Action (IdeResult TcModuleResult)
+ Development.IDE.Core.Shake: [lspEnv] :: ShakeExtras -> Maybe (LanguageContextEnv Config)
+ Development.IDE.Core.Shake: mRunLspT :: Applicative m => Maybe (LanguageContextEnv c) -> LspT c m () -> m ()
+ Development.IDE.Core.Shake: mRunLspTCallback :: Monad m => Maybe (LanguageContextEnv c) -> (LspT c m a -> LspT c m a) -> m a -> m a
+ Development.IDE.GHC.ExactPrint: graftSmallestDecls :: forall a. HasDecls a => SrcSpan -> [LHsDecl GhcPs] -> Graft (Either String) a
+ Development.IDE.GHC.ExactPrint: graftSmallestDeclsWithM :: forall a. HasDecls a => SrcSpan -> (LHsDecl GhcPs -> TransformT (Either String) (Maybe [LHsDecl GhcPs])) -> Graft (Either String) a
+ Development.IDE.GHC.ExactPrint: graftWithoutParentheses :: forall ast a. (Data a, ASTElement ast) => SrcSpan -> Located ast -> Graft (Either String) a
+ Development.IDE.Import.DependencyInformation: newtype ModuleImports
+ Development.IDE.Import.DependencyInformation: newtype TransitiveDependencies
+ Development.IDE.Import.FindImports: ArtifactsLocation :: !NormalizedFilePath -> !Maybe ModLocation -> !Bool -> ArtifactsLocation
+ Development.IDE.Import.FindImports: FileImport :: !ArtifactsLocation -> Import
+ Development.IDE.Import.FindImports: PackageImport :: Import
+ Development.IDE.Import.FindImports: [artifactFilePath] :: ArtifactsLocation -> !NormalizedFilePath
+ Development.IDE.Import.FindImports: [artifactIsSource] :: ArtifactsLocation -> !Bool
+ Development.IDE.Import.FindImports: [artifactModLocation] :: ArtifactsLocation -> !Maybe ModLocation
+ Development.IDE.Import.FindImports: data ArtifactsLocation
+ Development.IDE.Import.FindImports: data Import
+ Development.IDE.Import.FindImports: instance Control.DeepSeq.NFData Development.IDE.Import.FindImports.ArtifactsLocation
+ Development.IDE.Import.FindImports: instance Control.DeepSeq.NFData Development.IDE.Import.FindImports.Import
+ Development.IDE.Import.FindImports: instance GHC.Show.Show Development.IDE.Import.FindImports.ArtifactsLocation
+ Development.IDE.Import.FindImports: instance GHC.Show.Show Development.IDE.Import.FindImports.Import
+ Development.IDE.Import.FindImports: isBootLocation :: ArtifactsLocation -> Bool
+ Development.IDE.Import.FindImports: locateModule :: MonadIO m => DynFlags -> [(InstalledUnitId, DynFlags)] -> [String] -> (ModuleName -> NormalizedFilePath -> m Bool) -> Located ModuleName -> Maybe FastString -> Bool -> m (Either [FileDiagnostic] Import)
+ Development.IDE.Import.FindImports: locateModuleFile :: MonadIO m => [[FilePath]] -> [String] -> (ModuleName -> NormalizedFilePath -> m Bool) -> Bool -> ModuleName -> m (Maybe NormalizedFilePath)
+ Development.IDE.Import.FindImports: mkImportDirs :: DynFlags -> (InstalledUnitId, DynFlags) -> Maybe (PackageName, [FilePath])
+ Development.IDE.Import.FindImports: modSummaryToArtifactsLocation :: NormalizedFilePath -> Maybe ModSummary -> ArtifactsLocation
+ Development.IDE.LSP.HoverDefinition: setIdeHandlers :: Handlers (ServerM c)
+ Development.IDE.LSP.Server: ReactorNotification :: IO () -> ReactorMessage
+ Development.IDE.LSP.Server: ReactorRequest :: SomeLspId -> IO () -> (ResponseError -> IO ()) -> ReactorMessage
+ Development.IDE.LSP.Server: data ReactorMessage
+ Development.IDE.LSP.Server: notificationHandler :: forall (m :: Method FromClient Notification) c. HasTracing (MessageParams m) => SMethod m -> (IdeState -> MessageParams m -> LspM c ()) -> Handlers (ServerM c)
+ Development.IDE.LSP.Server: requestHandler :: forall (m :: Method FromClient Request) c. HasTracing (MessageParams m) => SMethod m -> (IdeState -> MessageParams m -> LspM c (Either ResponseError (ResponseResult m))) -> Handlers (ServerM c)
+ Development.IDE.LSP.Server: type ReactorChan = Chan ReactorMessage
+ Development.IDE.LSP.Server: type ServerM c = ReaderT (ReactorChan, IdeState) (LspM c)
+ Development.IDE.Main: Arguments :: Bool -> Maybe [FilePath] -> Logger -> Rules () -> IdePlugins IdeState -> Plugin Config -> SessionLoadingOptions -> (Maybe Config -> Action IdeGhcSession -> IdeOptions) -> Options -> Config -> (FilePath -> IO FilePath) -> Arguments
+ Development.IDE.Main: [argFiles] :: Arguments -> Maybe [FilePath]
+ Development.IDE.Main: [argsDefaultHlsConfig] :: Arguments -> Config
+ Development.IDE.Main: [argsGetHieDbLoc] :: Arguments -> FilePath -> IO FilePath
+ Development.IDE.Main: [argsGhcidePlugin] :: Arguments -> Plugin Config
+ Development.IDE.Main: [argsHlsPlugins] :: Arguments -> IdePlugins IdeState
+ Development.IDE.Main: [argsIdeOptions] :: Arguments -> Maybe Config -> Action IdeGhcSession -> IdeOptions
+ Development.IDE.Main: [argsLogger] :: Arguments -> Logger
+ Development.IDE.Main: [argsLspOptions] :: Arguments -> Options
+ Development.IDE.Main: [argsOTMemoryProfiling] :: Arguments -> Bool
+ Development.IDE.Main: [argsRules] :: Arguments -> Rules ()
+ Development.IDE.Main: [argsSessionLoadingOptions] :: Arguments -> SessionLoadingOptions
+ Development.IDE.Main: data Arguments
+ Development.IDE.Main: defaultMain :: Arguments -> IO ()
+ Development.IDE.Main: instance Data.Default.Class.Default Development.IDE.Main.Arguments
+ Development.IDE.Plugin: [pluginHandlers] :: Plugin c -> Handlers (ServerM c)
+ Development.IDE.Plugin.HLS: instance GHC.Base.Monoid Development.IDE.Plugin.HLS.IdeHandlers
+ Development.IDE.Plugin.HLS: instance GHC.Base.Semigroup Development.IDE.Plugin.HLS.IdeHandlers
+ Development.IDE.Session: [getInitialGhcLibDir] :: SessionLoadingOptions -> IO (Maybe LibDir)
+ Development.IDE.Session: instance Data.Default.Class.Default Development.IDE.Session.SessionLoadingOptions
+ Development.IDE.Types.HscEnvEq: envVisibleModuleNames :: HscEnvEq -> IO (Maybe [ModuleName])
- Development.IDE: Diagnostic :: Range -> Maybe DiagnosticSeverity -> Maybe NumberOrString -> Maybe DiagnosticSource -> Text -> Maybe (List DiagnosticTag) -> Maybe (List DiagnosticRelatedInformation) -> Diagnostic
+ Development.IDE: Diagnostic :: Range -> Maybe DiagnosticSeverity -> Maybe (Int |? Text) -> Maybe DiagnosticSource -> Text -> Maybe (List DiagnosticTag) -> Maybe (List DiagnosticRelatedInformation) -> Diagnostic
- Development.IDE: Modified :: FileOfInterestStatus
+ Development.IDE: Modified :: !Bool -> FileOfInterestStatus
- Development.IDE: Plugin :: Rules () -> PartialHandlers c -> Plugin c
+ Development.IDE: Plugin :: Rules () -> Handlers (ServerM c) -> Plugin c
- Development.IDE: [$sel:_code:Diagnostic] :: Diagnostic -> Maybe NumberOrString
+ Development.IDE: [$sel:_code:Diagnostic] :: Diagnostic -> Maybe (Int |? Text)
- Development.IDE: getClientConfigAction :: (Default a, FromJSON a) => Action a
+ Development.IDE: getClientConfigAction :: Config -> Action Config
- Development.IDE.Core.FileStore: makeLSPVFSHandle :: LspFuncs c -> VFSHandle
+ Development.IDE.Core.FileStore: makeLSPVFSHandle :: LanguageContextEnv c -> VFSHandle
- Development.IDE.Core.OfInterest: Modified :: FileOfInterestStatus
+ Development.IDE.Core.OfInterest: Modified :: !Bool -> FileOfInterestStatus
- Development.IDE.Core.RuleTypes: Modified :: FileOfInterestStatus
+ Development.IDE.Core.RuleTypes: Modified :: !Bool -> FileOfInterestStatus
- Development.IDE.Core.Rules: TransitiveDependencies :: [NormalizedFilePath] -> [NamedModuleDep] -> [InstalledUnitId] -> TransitiveDependencies
+ Development.IDE.Core.Rules: TransitiveDependencies :: [NormalizedFilePath] -> TransitiveDependencies
- Development.IDE.Core.Rules: getClientConfigAction :: (Default a, FromJSON a) => Action a
+ Development.IDE.Core.Rules: getClientConfigAction :: Config -> Action Config
- Development.IDE.Core.Service: initialise :: ClientCapabilities -> Rules () -> IO LspId -> (FromServerMessage -> IO ()) -> WithProgressFunc -> WithIndefiniteProgressFunc -> Logger -> Debouncer NormalizedUri -> IdeOptions -> VFSHandle -> HieDb -> IndexQueue -> IO IdeState
+ Development.IDE.Core.Service: initialise :: Rules () -> Maybe (LanguageContextEnv Config) -> Logger -> Debouncer NormalizedUri -> IdeOptions -> VFSHandle -> HieDb -> IndexQueue -> IO IdeState
- 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 -> ClientCapabilities -> HieDb -> HieDbWriter -> Var (HashMap Key GetStalePersistent) -> IO LspId -> VFSHandle -> ShakeExtras
+ Development.IDE.Core.Shake: ShakeExtras :: Maybe (LanguageContextEnv Config) -> 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 -> ([DelayedAction ()] -> IO ()) -> IORef NameCache -> Var (Hashed KnownTargets) -> Var ExportsMap -> ActionQueue -> ClientCapabilities -> HieDb -> HieDbWriter -> Var (HashMap Key GetStalePersistent) -> VFSHandle -> ShakeExtras
- Development.IDE.Core.Shake: shakeOpen :: IO LspId -> (FromServerMessage -> IO ()) -> WithProgressFunc -> WithIndefiniteProgressFunc -> ClientCapabilities -> Logger -> Debouncer NormalizedUri -> Maybe FilePath -> IdeReportProgress -> IdeTesting -> HieDb -> IndexQueue -> VFSHandle -> ShakeOptions -> Rules () -> IO IdeState
+ Development.IDE.Core.Shake: shakeOpen :: Maybe (LanguageContextEnv Config) -> Logger -> Debouncer NormalizedUri -> Maybe FilePath -> IdeReportProgress -> IdeTesting -> HieDb -> IndexQueue -> VFSHandle -> ShakeOptions -> Rules () -> IO IdeState
- Development.IDE.Core.Shake: type WithProgressFunc = forall a. Text -> ProgressCancellable -> ((Progress -> IO ()) -> IO a) -> IO a
+ Development.IDE.Core.Shake: type WithProgressFunc = forall a. Text -> ProgressCancellable -> ((ProgressAmount -> IO ()) -> IO a) -> IO a
- Development.IDE.Core.Tracing: otTracedHandler :: String -> String -> (SpanInFlight -> IO a) -> IO a
+ Development.IDE.Core.Tracing: otTracedHandler :: MonadUnliftIO m => String -> String -> (SpanInFlight -> m a) -> m a
- Development.IDE.Core.Tracing: otTracedProvider :: PluginId -> ByteString -> IO a -> IO a
+ Development.IDE.Core.Tracing: otTracedProvider :: MonadUnliftIO m => PluginId -> ByteString -> m a -> m a
- Development.IDE.Import.DependencyInformation: DependencyInformation :: !FilePathIdMap (NonEmpty NodeError) -> !FilePathIdMap ShowableModuleName -> !FilePathIdMap FilePathIdSet -> !IntMap IntSet -> !FilePathIdMap (Set InstalledUnitId) -> !PathIdMap -> !BootIdMap -> DependencyInformation
+ Development.IDE.Import.DependencyInformation: DependencyInformation :: !FilePathIdMap (NonEmpty NodeError) -> !FilePathIdMap ShowableModuleName -> !FilePathIdMap FilePathIdSet -> !IntMap IntSet -> !PathIdMap -> !BootIdMap -> DependencyInformation
- Development.IDE.Import.DependencyInformation: ModuleImports :: ![(Located ModuleName, Maybe FilePathId)] -> !Set InstalledUnitId -> ModuleImports
+ Development.IDE.Import.DependencyInformation: ModuleImports :: [(Located ModuleName, Maybe FilePathId)] -> ModuleImports
- Development.IDE.Import.DependencyInformation: TransitiveDependencies :: [NormalizedFilePath] -> [NamedModuleDep] -> [InstalledUnitId] -> TransitiveDependencies
+ Development.IDE.Import.DependencyInformation: TransitiveDependencies :: [NormalizedFilePath] -> TransitiveDependencies
- Development.IDE.Import.DependencyInformation: [moduleImports] :: ModuleImports -> ![(Located ModuleName, Maybe FilePathId)]
+ Development.IDE.Import.DependencyInformation: [moduleImports] :: ModuleImports -> [(Located ModuleName, Maybe FilePathId)]
- Development.IDE.LSP.HoverDefinition: gotoDefinition :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError LocationResponseParams)
+ Development.IDE.LSP.HoverDefinition: gotoDefinition :: IdeState -> TextDocumentPositionParams -> LspM c (Either ResponseError (ResponseResult TextDocumentDefinition))
- Development.IDE.LSP.HoverDefinition: gotoTypeDefinition :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError LocationResponseParams)
+ Development.IDE.LSP.HoverDefinition: gotoTypeDefinition :: IdeState -> TextDocumentPositionParams -> LspM c (Either ResponseError (ResponseResult TextDocumentTypeDefinition))
- Development.IDE.LSP.HoverDefinition: hover :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError (Maybe Hover))
+ Development.IDE.LSP.HoverDefinition: hover :: IdeState -> TextDocumentPositionParams -> LspM c (Either ResponseError (Maybe Hover))
- 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.LSP.LanguageServer: runLanguageServer :: forall config. Show config => Options -> (FilePath -> IO FilePath) -> (IdeState -> Value -> IO (Either Text config)) -> Handlers (ServerM config) -> (LanguageContextEnv config -> VFSHandle -> Maybe FilePath -> HieDb -> IndexQueue -> IO IdeState) -> IO ()
- Development.IDE.LSP.Outline: moduleOutline :: LspFuncs c -> IdeState -> DocumentSymbolParams -> IO (Either ResponseError DSResult)
+ Development.IDE.LSP.Outline: moduleOutline :: IdeState -> DocumentSymbolParams -> LspM c (Either ResponseError (List DocumentSymbol |? List SymbolInformation))
- Development.IDE.Plugin: Plugin :: Rules () -> PartialHandlers c -> Plugin c
+ Development.IDE.Plugin: Plugin :: Rules () -> Handlers (ServerM c) -> Plugin c
- Development.IDE.Session: SessionLoadingOptions :: (FilePath -> IO (Maybe FilePath)) -> (FilePath -> IO (Cradle Void)) -> (String -> [String] -> IO CacheDirs) -> SessionLoadingOptions
+ Development.IDE.Session: SessionLoadingOptions :: (FilePath -> IO (Maybe FilePath)) -> (FilePath -> IO (Cradle Void)) -> (String -> [String] -> IO CacheDirs) -> IO (Maybe LibDir) -> SessionLoadingOptions
- Development.IDE.Session: setInitialDynFlags :: IO (Maybe LibDir)
+ Development.IDE.Session: setInitialDynFlags :: SessionLoadingOptions -> IO (Maybe LibDir)
- Development.IDE.Spans.Documentation: getDocumentationTryGhc :: HscEnv -> Module -> [ParsedModule] -> Name -> IO SpanDoc
+ Development.IDE.Spans.Documentation: getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
- Development.IDE.Spans.Documentation: getDocumentationsTryGhc :: HscEnv -> Module -> [ParsedModule] -> [Name] -> IO [SpanDoc]
+ Development.IDE.Spans.Documentation: getDocumentationsTryGhc :: HscEnv -> Module -> [Name] -> IO [SpanDoc]
- Development.IDE.Spans.Documentation: mkDocMap :: HscEnv -> [ParsedModule] -> RefMap a -> TcGblEnv -> IO DocAndKindMap
+ Development.IDE.Spans.Documentation: mkDocMap :: HscEnv -> RefMap a -> TcGblEnv -> IO DocAndKindMap
- Development.IDE.Types.Diagnostics: Diagnostic :: Range -> Maybe DiagnosticSeverity -> Maybe NumberOrString -> Maybe DiagnosticSource -> Text -> Maybe (List DiagnosticTag) -> Maybe (List DiagnosticRelatedInformation) -> Diagnostic
+ Development.IDE.Types.Diagnostics: Diagnostic :: Range -> Maybe DiagnosticSeverity -> Maybe (Int |? Text) -> Maybe DiagnosticSource -> Text -> Maybe (List DiagnosticTag) -> Maybe (List DiagnosticRelatedInformation) -> Diagnostic
- Development.IDE.Types.Diagnostics: [$sel:_code:Diagnostic] :: Diagnostic -> Maybe NumberOrString
+ Development.IDE.Types.Diagnostics: [$sel:_code:Diagnostic] :: Diagnostic -> Maybe (Int |? Text)
- Development.IDE.Types.HscEnvEq: envPackageExports :: HscEnvEq -> ExportsMap
+ Development.IDE.Types.HscEnvEq: envPackageExports :: HscEnvEq -> IO ExportsMap
- Development.IDE.Types.Options: IdeOptions :: (ParsedSource -> IdePreprocessedSource) -> Action IdeGhcSession -> IdePkgLocationOptions -> [String] -> Maybe FilePath -> IdeOTMemoryProfiling -> IdeTesting -> IdeReportProgress -> String -> Bool -> [Text] -> IdeDefer -> !Bool -> CheckParents -> OptHaddockParse -> (DynFlags -> DynFlags) -> ShakeOptions -> IdeOptions
+ Development.IDE.Types.Options: IdeOptions :: (ParsedSource -> IdePreprocessedSource) -> Action IdeGhcSession -> IdePkgLocationOptions -> [String] -> Maybe FilePath -> IdeOTMemoryProfiling -> IdeTesting -> IdeReportProgress -> String -> Bool -> [Text] -> IdeDefer -> IO Bool -> IO CheckParents -> OptHaddockParse -> (DynFlags -> DynFlags) -> ShakeOptions -> IdeOptions
- Development.IDE.Types.Options: [optCheckParents] :: IdeOptions -> CheckParents
+ Development.IDE.Types.Options: [optCheckParents] :: IdeOptions -> IO CheckParents
- Development.IDE.Types.Options: [optCheckProject] :: IdeOptions -> !Bool
+ Development.IDE.Types.Options: [optCheckProject] :: IdeOptions -> IO Bool

Files

CHANGELOG.md view
@@ -1,3 +1,15 @@+### 0.7.5 (2021-02-??)+* Tone down some logInfos to logDebug (#1385) - Pepe Iborra+* Show window message when auto extending import lists (#1371) - Potato Hatsue+* Catch GHC errors in listing module names (#1367) - Potato Hatsue+* Upgrade to lsp-1.0 (#1284) - wz1000+* Added Development.IDE.Main (#1338) - Pepe Iborra+* Fix completion snippets on DuplicateRecordFields (#1360) - Potato Hatsue+* Add code action for hiding shadowed identifiers from imports (#1322) - Potato Hatsue+* Make find-definition work better with multi-components (#1357) - wz1000+* Index files on first open (#1358) - wz1000+* Fix code actions regression (#1349) - Pepe Iborra+ ### 0.7.4 (2021-02-08) * Support for references via hiedb (#704) - wz1000 * Fix space leak on cradle reloads (#1316) - Pepe Iborra
bench/hist/Main.hs view
@@ -55,6 +55,7 @@ import System.Console.GetOpt import Data.Maybe import Control.Monad.Extra+import System.FilePath   configPath :: FilePath@@ -84,7 +85,12 @@       _ -> want wants  ghcideBuildRules :: MkBuildRules BuildSystem-ghcideBuildRules = MkBuildRules findGhcForBuildSystem "ghcide" buildGhcide+ghcideBuildRules = MkBuildRules findGhcForBuildSystem "ghcide" projectDepends buildGhcide+  where+      projectDepends = do+        need . map ("src" </>) =<< getDirectoryFiles "src" ["//*.hs"]+        need . map ("session-loader" </>) =<< getDirectoryFiles "session-loader" ["//*.hs"]+        need =<< getDirectoryFiles "." ["*.cabal"]  -------------------------------------------------------------------------------- @@ -116,7 +122,7 @@   let build = outputFolder configStatic    buildRules build ghcideBuildRules-  benchRules build (MkBenchRules (askOracle $ GetSamples ()) benchGhcide "ghcide")+  benchRules build (MkBenchRules (askOracle $ GetSamples ()) benchGhcide warmupGhcide "ghcide")   csvRules build   svgRules build   heapProfileRules build@@ -141,6 +147,7 @@         ,"--install-method=copy"         ,"--overwrite-policy=always"         ,"--ghc-options=-rtsopts"+        ,"--ghc-options=-eventlog"         ]  buildGhcide Stack args out =@@ -150,6 +157,7 @@         ,"ghcide:ghcide"         ,"--copy-bins"         ,"--ghc-options=-rtsopts"+        ,"--ghc-options=-eventlog"         ]  benchGhcide@@ -170,3 +178,15 @@     [ "--stack" | Stack == buildSystem     ] +warmupGhcide :: BuildSystem -> FilePath -> [CmdOption] -> Example -> Action ()+warmupGhcide buildSystem exePath args example = do+  command args "ghcide-bench" $+    [ "--no-clean",+      "-v",+      "--samples=1",+      "--ghcide=" <> exePath,+      "--select=hover"+    ] +++    exampleToOptions example +++    [ "--stack" | Stack == buildSystem+    ]
bench/lib/Experiments.hs view
@@ -1,7 +1,9 @@ {-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE ExistentialQuantification #-} {-# LANGUAGE ImplicitParams #-} {-# LANGUAGE ImpredicativeTypes #-}+{-# OPTIONS_GHC -Wno-deprecations -Wno-unticked-promoted-constructors #-}  module Experiments ( Bench(..)@@ -20,19 +22,19 @@ , exampleToOptions ) where import Control.Applicative.Combinators (skipManyTill)-import Control.Exception.Safe+import Control.Exception.Safe (IOException, handleAny, try) import Control.Monad.Extra import Control.Monad.IO.Class-import Data.Aeson (Value(Null))+import Data.Aeson (Value(Null), toJSON) import Data.List import Data.Maybe import qualified Data.Text as T import Data.Version import Development.IDE.Plugin.Test import Experiments.Types-import Language.Haskell.LSP.Test-import Language.Haskell.LSP.Types-import Language.Haskell.LSP.Types.Capabilities+import Language.LSP.Test+import Language.LSP.Types+import Language.LSP.Types.Capabilities import Numeric.Natural import Options.Applicative import System.Directory@@ -41,6 +43,7 @@ import System.Process import System.Time.Extra import Text.ParserCombinators.ReadP (readP_to_S)+import Development.Shake (cmd_, CmdOption (Cwd, FileStdout))  charEdit :: Position -> TextDocumentContentChangeEvent charEdit p =@@ -78,13 +81,13 @@           isJust <$> getHover doc (fromJust identifierP),       ---------------------------------------------------------------------------------------       bench "getDefinition" $ allWithIdentifierPos $ \DocumentPositions{..} ->-        not . null <$> getDefinitions doc (fromJust identifierP),+        either (not . null) (not . null) . toEither <$> getDefinitions doc (fromJust identifierP),       ---------------------------------------------------------------------------------------       bench "getDefinition after edit" $ \docs -> do           forM_ docs $ \DocumentPositions{..} ->             changeDoc doc [charEdit stringLiteralP]           flip allWithIdentifierPos docs $ \DocumentPositions{..} ->-            not . null <$> getDefinitions doc (fromJust identifierP),+            either (not . null) (not . null) . toEither <$> getDefinitions doc (fromJust identifierP),       ---------------------------------------------------------------------------------------       bench "documentSymbols" $ allM $ \DocumentPositions{..} -> do         fmap (either (not . null) (not . null)) . getDocumentSymbols $ doc,@@ -147,7 +150,7 @@         ( \docs -> do             Just hieYaml <- uriToFilePath <$> getDocUri "hie.yaml"             liftIO $ appendFile hieYaml "##\n"-            sendNotification WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $+            sendNotification SWorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $                 List [ FileEvent (filePathToUri "hie.yaml") FcChanged ]             forM_ docs $ \DocumentPositions{..} ->               changeDoc doc [charEdit stringLiteralP]@@ -162,7 +165,7 @@         (\docs -> do             Just hieYaml <- uriToFilePath <$> getDocUri "hie.yaml"             liftIO $ appendFile hieYaml "##\n"-            sendNotification WorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $+            sendNotification SWorkspaceDidChangeWatchedFiles $ DidChangeWatchedFilesParams $                 List [ FileEvent (filePathToUri "hie.yaml") FcChanged ]             flip allWithIdentifierPos docs $ \DocumentPositions{..} -> isJust <$> getHover doc (fromJust identifierP)         )@@ -358,7 +361,9 @@ waitForProgressDone = loop   where     loop = do-      void (skipManyTill anyMessage message :: Session WorkDoneProgressEndNotification)+      ~() <- skipManyTill anyMessage $ satisfyMaybe $ \case+        FromServerMess SProgress (NotificationMessage _ _ (ProgressParams _ (End _))) -> Just ()+        _ -> Nothing       done <- null <$> getIncompleteProgressSessions       unless done loop @@ -392,8 +397,9 @@             else do                 output (showDuration t)                 -- Wait for the delayed actions to finish-                waitId <- sendRequest (CustomClientMethod "test") WaitForShakeQueue-                (td, resp) <- duration $ skipManyTill anyMessage $ responseForId waitId+                let m = SCustomMethod "test"+                waitId <- sendRequest m (toJSON WaitForShakeQueue)+                (td, resp) <- duration $ skipManyTill anyMessage $ responseForId m waitId                 case resp of                     ResponseMessage{_result=Right Null} -> do                       loop (userWaits+t) (delayedWork+td) (n -1)@@ -423,19 +429,24 @@ setup = do --   when alreadyExists $ removeDirectoryRecursive examplesPath   benchDir <- case example ?config of-      UsePackage{..} -> return examplePath+      UsePackage{..} -> do+          let hieYamlPath = examplePath </> "hie.yaml"+          alreadyExists <- doesFileExist hieYamlPath+          unless alreadyExists $+                cmd_ (Cwd examplePath) (FileStdout hieYamlPath) ("gen-hie"::String)+          return examplePath       GetPackage{..} -> do         let path = examplesPath </> package             package = exampleName <> "-" <> showVersion exampleVersion+            hieYamlPath = path </> "hie.yaml"         alreadySetup <- doesDirectoryExist path         unless alreadySetup $           case buildTool ?config of             Cabal -> do                 let cabalVerbosity = "-v" ++ show (fromEnum (verbose ?config))                 callCommandLogging $ "cabal get " <> cabalVerbosity <> " " <> package <> " -d " <> examplesPath-                writeFile-                    (path </> "hie.yaml")-                    ("cradle: {cabal: {component: " <> exampleName <> "}}")+                let hieYamlPath = path </> "hie.yaml"+                cmd_ (Cwd path) (FileStdout hieYamlPath) ("gen-hie"::String)                 -- Need this in case there is a parent cabal.project somewhere                 writeFile                     (path </> "cabal.project")@@ -464,9 +475,7 @@                             ]                         ) -                writeFile-                    (path </> "hie.yaml")-                    ("cradle: {stack: {component: " <> show (exampleName <> ":lib") <> "}}")+                cmd_ (Cwd path) (FileStdout hieYamlPath) ("gen-hie"::String) ["--stack"::String]         return path    whenJust (shakeProfiling ?config) $ createDirectoryIfMissing True@@ -498,22 +507,21 @@          -- Find an identifier defined in another file in this project         symbols <- getDocumentSymbols doc-        case symbols of-            Left [DocumentSymbol{_children = Just (List symbols)}] -> do-                let endOfImports = case symbols of-                        DocumentSymbol{_kind = SkModule, _name = "imports", _range } : _ ->-                            Position (succ $ _line $ _end _range) 4-                        DocumentSymbol{_range} : _ -> _start _range-                        [] -> error "Module has no symbols"-                contents <- documentContents doc--                identifierP <- searchSymbol doc contents endOfImports--                return $ DocumentPositions{..}-            other ->-                error $ "symbols: " <> show other-+        let endOfImports = case symbols of+                Left symbols | Just x <- findEndOfImports symbols -> x+                _ -> error $ "symbols: " <> show symbols+        contents <- documentContents doc+        identifierP <- searchSymbol doc contents endOfImports+        return $ DocumentPositions{..} +findEndOfImports :: [DocumentSymbol] -> Maybe Position+findEndOfImports (DocumentSymbol{_kind = SkModule, _name = "imports", _range} : _) =+    Just $ Position (succ $ _line $ _end _range) 4+findEndOfImports [DocumentSymbol{_kind = SkFile, _children = Just (List cc)}] =+    findEndOfImports cc+findEndOfImports (DocumentSymbol{_range} : _) =+    Just $ _start _range+findEndOfImports _ = Nothing  -------------------------------------------------------------------------------------------- @@ -559,7 +567,7 @@       checkDefinitions pos = do         defs <- getDefinitions doc pos         case defs of-            [Location uri _] -> return $ uri /= _uri+            (InL [Location uri _]) -> return $ uri /= _uri             _ -> return False       checkCompletions pos =         not . null <$> getCompletions doc pos
exe/Main.hs view
@@ -5,61 +5,34 @@  module Main(main) where -import Arguments-import Control.Concurrent.Extra-import Control.Monad.Extra-import Control.Exception.Safe-import Control.Lens ( (^.) )-import Data.Default-import Data.List.Extra-import Data.Maybe+import Arguments ( Arguments'(..), IdeCmd(..), getArguments )+import Control.Concurrent.Extra ( newLock, withLock )+import Control.Monad.Extra ( unless, when, whenJust )+import Data.Default ( Default(def) )+import Data.List.Extra ( upper )+import Data.Maybe (fromMaybe) import qualified Data.Text as T import qualified Data.Text.IO as T-import Data.Version-import Development.IDE.Core.Debouncer-import Development.IDE.Core.FileStore-import Development.IDE.Core.OfInterest-import Development.IDE.Core.Service-import Development.IDE.Core.Rules-import Development.IDE.Core.Shake-import Development.IDE.Core.RuleTypes-import Development.IDE.LSP.Protocol-import Development.IDE.Types.Location-import Development.IDE.Types.Diagnostics+import Data.Version ( showVersion )+import Development.GitRev ( gitHash )+import Development.IDE ( Logger(Logger), Priority(Info), action )+import Development.IDE.Core.OfInterest (kick)+import Development.IDE.Core.Rules (mainRule)+import qualified Development.IDE.Plugin.HLS.GhcIde as GhcIde+import qualified Development.IDE.Plugin.Test as Test+import Development.IDE.Session (setInitialDynFlags, getHieDbLoc) import Development.IDE.Types.Options-import Development.IDE.Types.Logger-import Development.IDE.Plugin-import Development.IDE.Plugin.Test as Test-import Development.IDE.Session (loadSession, setInitialDynFlags, getHieDbLoc, runWithDb)-import Development.Shake (ShakeOptions (shakeThreads))-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-import System.IO-import System.Info-import System.Exit-import System.FilePath-import System.Time.Extra-import Paths_ghcide-import Development.GitRev-import qualified Data.HashMap.Strict as HashMap-import qualified Data.Aeson as J--import HIE.Bios.Cradle-import Development.IDE (action)-import Text.Printf-import Development.IDE.Core.Tracing-import Development.IDE.Types.Shake (Key(Key))-import Development.IDE.Plugin.HLS (asGhcIdePlugin)-import Development.IDE.Plugin.HLS.GhcIde as GhcIde-import Ide.Plugin.Config-import Ide.PluginUtils (allLspCmdIds', getProcessID, pluginDescToIdePlugins)-+import qualified Development.IDE.Main as Main+import Development.Shake (ShakeOptions(shakeThreads))+import Ide.Plugin.Config (Config(checkParents, checkProject))+import Ide.PluginUtils (pluginDescToIdePlugins) import HieDb.Run (Options(..), runCommand)+import Paths_ghcide ( version )+import qualified System.Directory.Extra as IO+import System.Environment ( getExecutablePath )+import System.Exit ( ExitCode(ExitFailure), exitSuccess, exitWith )+import System.Info ( compilerVersion )+import System.IO ( stderr, hPutStrLn )  ghcideVersion :: IO String ghcideVersion = do@@ -83,171 +56,62 @@      whenJust argsCwd IO.setCurrentDirectory --    dir <- IO.getCurrentDirectory-    dbLoc <- getHieDbLoc dir+    -- lock to avoid overlapping output on stdout+    lock <- newLock+    let logger = Logger $ \pri msg -> when (pri >= logLevel) $ withLock lock $+            T.putStrLn $ T.pack ("[" ++ upper (show pri) ++ "] ") <> msg+        logLevel = if argsVerbose then minBound else Info      case argFilesOrCmd of       DbCmd opts cmd -> do-        mlibdir <- setInitialDynFlags+        dir <- IO.getCurrentDirectory+        dbLoc <- getHieDbLoc dir+        mlibdir <- setInitialDynFlags def         case mlibdir of           Nothing -> exitWith $ ExitFailure 1-          Just libdir ->-            runCommand libdir opts{database = dbLoc} cmd-      Typecheck (Just -> argFilesOrCmd) | not argLSP -> runWithDb dbLoc $ runIde Arguments{..}-      _ -> let argFilesOrCmd = Nothing in runWithDb dbLoc $ runIde Arguments{..}---runIde :: Arguments' (Maybe [FilePath]) -> HieDb -> IndexQueue -> IO ()-runIde Arguments{..} hiedb hiechan = do-    -- lock to avoid overlapping output on stdout-    lock <- newLock-    let logger p = Logger $ \pri msg -> when (pri >= p) $ withLock lock $-            T.putStrLn $ T.pack ("[" ++ upper (show pri) ++ "] ") <> msg--    dir <- IO.getCurrentDirectory+          Just libdir -> runCommand libdir opts{database = dbLoc} cmd -    let hlsPlugins = pluginDescToIdePlugins $-            GhcIde.descriptors ++-            [ Test.blockCommandDescriptor "block-command" | argsTesting]+      _ -> do -    pid <- T.pack . show <$> getProcessID-    let hlsPlugin = asGhcIdePlugin hlsPlugins-        hlsCommands = allLspCmdIds' pid hlsPlugins+          case argFilesOrCmd of+              LSP -> do+                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!"+              _ -> return () -    let plugins = hlsPlugin-            <> if argsTesting then Test.plugin else mempty-        onInitialConfiguration :: InitializeRequest -> Either T.Text Config-        onInitialConfiguration x = case x ^. params . initializationOptions of-          Nothing -> Right def-          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 hlsCommands-                      , LSP.completionTriggerCharacters = Just "."-                      }-    case argFilesOrCmd of-      Nothing -> do-        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 getConfig rootPath -> do-            t <- t-            hPutStrLn stderr $ "Started LSP server in " ++ showDuration t+          Main.defaultMain def+            {Main.argFiles = case argFilesOrCmd of+                Typecheck x | not argLSP -> Just x+                _ -> Nothing -            -- We want to set the global DynFlags right now, so that we can use-            -- `unsafeGlobalDynFlags` even before the project is configured-            -- We do it here since haskell-lsp changes our working directory to the correct place ('rootPath')-            -- before calling this function-            _mlibdir <- setInitialDynFlags-                          `catchAny` (\e -> (hPutStrLn stderr $ "setInitialDynFlags: " ++ displayException e) >> pure Nothing)+            ,Main.argsLogger = logger -            sessionLoader <- loadSession $ fromMaybe dir rootPath-            config <- fromMaybe def <$> getConfig-            let options = defOptions-                    { optReportProgress    = clientSupportsProgress caps-                    , optShakeProfiling    = argsShakeProfiling-                    , optOTMemoryProfiling = IdeOTMemoryProfiling argsOTMemoryProfiling-                    , optTesting           = IdeTesting argsTesting-                    , optShakeOptions      = (optShakeOptions defOptions){shakeThreads = argsThreads}-                    , optCheckParents      = checkParents config-                    , optCheckProject      = checkProject config-                    }-                defOptions = defaultIdeOptions sessionLoader-                logLevel = if argsVerbose then minBound else Info-            debouncer <- newAsyncDebouncer-            let rules = do-                  -- install the main and ghcide-plugin rules-                  mainRule-                  pluginRules plugins-                  -- install the kick action, which triggers a typecheck on every-                  -- Shake database restart, i.e. on every user edit.-                  unless argsDisableKick $+            ,Main.argsRules = do+                -- install the main and ghcide-plugin rules+                mainRule+                -- install the kick action, which triggers a typecheck on every+                -- Shake database restart, i.e. on every user edit.+                unless argsDisableKick $                     action kick-            initialise caps rules-                getLspId event wProg wIndefProg (logger logLevel) debouncer options vfs hiedb hiechan-      Just argFiles -> do-        -- GHC produces messages with UTF8 in them, so make sure the terminal doesn't error-        hSetEncoding stdout utf8-        hSetEncoding stderr utf8 -        putStrLn $ "Ghcide setup tester in " ++ dir ++ "."-        putStrLn "Report bugs at https://github.com/haskell/ghcide/issues"--        putStrLn $ "\nStep 1/4: Finding files to test in " ++ dir-        files <- expandFiles (argFiles ++ ["." | null argFiles])-        -- LSP works with absolute file paths, so try and behave similarly-        files <- nubOrd <$> mapM IO.canonicalizePath files-        putStrLn $ "Found " ++ show (length files) ++ " files"--        putStrLn "\nStep 2/4: Looking for hie.yaml files that control setup"-        cradles <- mapM findCradle files-        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 ()))-        sessionLoader <- loadSession dir-        let options = defOptions-                    { optShakeProfiling    = argsShakeProfiling-                    -- , optOTMemoryProfiling = IdeOTMemoryProfiling argsOTMemoryProfiling-                    , optTesting           = IdeTesting argsTesting-                    , optShakeOptions      = (optShakeOptions defOptions){shakeThreads = argsThreads}-                    , optCheckParents      = NeverCheck-                    , optCheckProject      = False-                    }-            defOptions = defaultIdeOptions sessionLoader-            logLevel = if argsVerbose then minBound else Info-        ide <- initialise def mainRule (pure $ IdInt 0) (showEvent lock) dummyWithProg (const (const id)) (logger logLevel) debouncer options vfs hiedb hiechan--        putStrLn "\nStep 4/4: Type checking the files"-        setFilesOfInterest ide $ HashMap.fromList $ map ((, OnDisk) . toNormalizedFilePath') files-        results <- runAction "User TypeCheck" ide $ uses TypeCheck (map toNormalizedFilePath' files)-        _results <- runAction "GetHie" ide $ uses GetHieAst (map toNormalizedFilePath' files)-        _results <- runAction "GenerateCore" ide $ uses GenerateCore (map toNormalizedFilePath' files)-        let (worked, failed) = partition fst $ zip (map isJust results) files-        when (failed /= []) $-            putStr $ unlines $ "Files that failed:" : map ((++) " * " . snd) failed--        let nfiles xs = let n = length xs in if n == 1 then "1 file" else show n ++ " files"-        putStrLn $ "\nCompleted (" ++ nfiles worked ++ " worked, " ++ nfiles failed ++ " failed)"--        when argsOTMemoryProfiling $ do-            let valuesRef = state $ shakeExtras ide-            values <- readVar valuesRef-            let consoleObserver Nothing = return $ \size -> printf "Total: %.2fMB\n" (fromIntegral @Int @Double size / 1e6)-                consoleObserver (Just k) = return $ \size -> printf "  - %s: %.2fKB\n" (show k) (fromIntegral @Int @Double size / 1e3)--            printf "# Shake value store contents(%d):\n" (length values)-            let keys = nub-                     $ Key GhcSession : Key GhcSessionDeps-                     : [ k | (_,k) <- HashMap.keys values, k /= Key GhcSessionIO]-                     ++ [Key GhcSessionIO]-            measureMemory (logger logLevel) [keys] consoleObserver valuesRef--        unless (null failed) (exitWith $ ExitFailure (length failed))+            ,Main.argsHlsPlugins =+                pluginDescToIdePlugins $+                GhcIde.descriptors+                ++ [Test.blockCommandDescriptor "block-command" | argsTesting] -{-# ANN runIde ("HLint: ignore Use nubOrd" :: String) #-}+            ,Main.argsGhcidePlugin = if argsTesting+                then Test.plugin+                else mempty -expandFiles :: [FilePath] -> IO [FilePath]-expandFiles = concatMapM $ \x -> do-    b <- IO.doesFileExist x-    if b then return [x] else do-        let recurse "." = True-            recurse x | "." `isPrefixOf` takeFileName x = False -- skip .git etc-            recurse x = takeFileName x `notElem` ["dist","dist-newstyle"] -- cabal directories-        files <- filter (\x -> takeExtension x `elem` [".hs",".lhs"]) <$> IO.listFilesInside (return . recurse) x-        when (null files) $-            fail $ "Couldn't find any .hs/.lhs files inside directory: " ++ x-        return files+            ,Main.argsIdeOptions = \(fromMaybe def -> config) sessionLoader ->+                let defOptions = defaultIdeOptions sessionLoader+                in defOptions+                  { optShakeProfiling = argsShakeProfiling+                  , optOTMemoryProfiling = IdeOTMemoryProfiling argsOTMemoryProfiling+                  , optTesting = IdeTesting argsTesting+                  , optShakeOptions = (optShakeOptions defOptions){shakeThreads = argsThreads}+                  , optCheckParents = pure $ checkParents config+                  , optCheckProject = pure $ checkProject config+                  }+            } --- | Print an LSP event.-showEvent :: Lock -> FromServerMessage -> IO ()-showEvent _ (EventFileDiagnostics _ []) = return ()-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.7.4.0+version:            0.7.5.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.4 || == 8.6.5 || == 8.8.2 || == 8.8.3 || == 8.8.4 || == 8.10.1 || == 8.10.2 || == 8.10.3+tested-with:        GHC == 8.6.4 || == 8.6.5 || == 8.8.2 || == 8.8.3 || == 8.8.4 || == 8.10.2 || == 8.10.3 || == 8.10.4 extra-source-files: include/ghc-api-version.h README.md CHANGELOG.md                     test/data/hover/*.hs                     test/data/multi/cabal.project@@ -27,11 +27,6 @@     type:     git     location: https://github.com/haskell/ghcide.git -flag ghc-lib-  description: build against ghc-lib instead of the ghc package-  default: False-  manual: True- library     default-language:   Haskell2010     build-depends:@@ -46,6 +41,8 @@         data-default,         deepseq,         directory,+        dependent-map,+        dependent-sum,         dlist,         extra >= 1.7.4,         fuzzy,@@ -55,12 +52,12 @@         Glob,         haddock-library >= 1.8,         hashable,-        haskell-lsp-types == 0.23.*,-        haskell-lsp == 0.23.*,         hie-compat,-        hls-plugin-api >= 0.7,+        hls-plugin-api >= 0.7.1,         lens,         hiedb == 0.3.0.1,+        lsp-types == 1.1.*,+        lsp == 1.1.1.0,         mtl,         network-uri,         parallel,@@ -88,19 +85,12 @@         vector,         bytestring-encoding,         opentelemetry >=0.6.1,-        heapsize ==0.3.*-    if flag(ghc-lib)-      build-depends:-        ghc-lib >= 8.8,-        ghc-lib-parser >= 8.8-      cpp-options: -DGHC_LIB-    else-      build-depends:+        heapsize ==0.3.*,+        unliftio,+        unliftio-core,         ghc-boot-th,         ghc-boot,         ghc >= 8.6,-        -- These dependencies are used by Development.IDE.Session and are-        -- Haskell specific. So don't use them when building with -fghc-lib!         ghc-check >=0.5.0.1,         ghc-paths,         cryptohash-sha1 >=0.11.100 && <0.12,@@ -131,13 +121,18 @@         TupleSections         TypeApplications         ViewPatterns+        DataKinds+        TypeOperators+        KindSignatures      hs-source-dirs:         src+        session-loader     include-dirs:         include     exposed-modules:         Development.IDE+        Development.IDE.Main         Development.IDE.Core.Debouncer         Development.IDE.Core.FileStore         Development.IDE.Core.IdeConfiguration@@ -150,16 +145,18 @@         Development.IDE.Core.Shake         Development.IDE.Core.Tracing         Development.IDE.GHC.Compat+        Development.IDE.Core.Compile         Development.IDE.GHC.Error         Development.IDE.GHC.ExactPrint         Development.IDE.GHC.Orphans         Development.IDE.GHC.Util         Development.IDE.Import.DependencyInformation+        Development.IDE.Import.FindImports         Development.IDE.LSP.HoverDefinition         Development.IDE.LSP.LanguageServer         Development.IDE.LSP.Outline-        Development.IDE.LSP.Protocol         Development.IDE.LSP.Server+        Development.IDE.Session         Development.IDE.Spans.Common         Development.IDE.Spans.Documentation         Development.IDE.Spans.AtPoint@@ -182,31 +179,16 @@         Development.IDE.Plugin.Test         Development.IDE.Plugin.TypeLenses -    -- Unfortunately, we cannot use loadSession with ghc-lib since hie-bios uses-    -- the real GHC library and the types are incompatible. Furthermore, when-    -- building with ghc-lib we need to make this Haskell agnostic, so no-    -- hie-bios!-    -- We also put these modules into a separate hs-source-dirs so we can avoid-    -- compiling them at all if ghc-lib is not set-    if !flag(ghc-lib)-      hs-source-dirs:-        session-loader-      exposed-modules:-        Development.IDE.Session-      other-modules:-        Development.IDE.Session.VersionCheck     other-modules:-        Development.IDE.Core.Compile         Development.IDE.Core.FileExists         Development.IDE.GHC.CPP         Development.IDE.GHC.Warnings-        Development.IDE.Import.FindImports         Development.IDE.LSP.Notifications         Development.IDE.Plugin.CodeAction.PositionIndexed         Development.IDE.Plugin.Completions.Logic-        Development.IDE.Plugin.HLS.Formatter+        Development.IDE.Session.VersionCheck         Development.IDE.Types.Action-    ghc-options: -Wall -Wno-name-shadowing -Wincomplete-uni-patterns+    ghc-options: -Wall -Wno-name-shadowing -Wincomplete-uni-patterns -Wno-unticked-promoted-constructors  executable ghcide-test-preprocessor     default-language: Haskell2010@@ -225,7 +207,8 @@     other-modules: Experiments.Types     build-tool-depends:         ghcide:ghcide-bench,-        hp2pretty:hp2pretty+        hp2pretty:hp2pretty,+        implicit-hie:gen-hie     default-extensions:         BangPatterns         DeriveFunctor@@ -254,8 +237,6 @@         yaml  executable ghcide-    if flag(ghc-lib)-      buildable: False     default-language:   Haskell2010     include-dirs:         include@@ -283,8 +264,8 @@         safe-exceptions,         ghc,         hashable,-        haskell-lsp,-        haskell-lsp-types,+        lsp,+        lsp-types,         heapsize,         hie-bios,         hls-plugin-api,@@ -315,13 +296,12 @@         ViewPatterns  test-suite ghcide-tests-    if flag(ghc-lib)-      buildable: False     type: exitcode-stdio-1.0     default-language: Haskell2010     build-tool-depends:         ghcide:ghcide,-        ghcide:ghcide-test-preprocessor+        ghcide:ghcide-test-preprocessor,+        implicit-hie:gen-hie     build-depends:         aeson,         base,@@ -343,12 +323,12 @@         ghcide,         ghc-typelits-knownnat,         haddock-library,-        haskell-lsp,-        haskell-lsp-types,+        lsp,+        lsp-types,         hls-plugin-api,         network-uri,         lens,-        lsp-test >= 0.12.0.0 && < 0.13,+        lsp-test == 0.13.0.0,         optparse-applicative,         process,         QuickCheck,@@ -369,7 +349,7 @@           record-hasfield     hs-source-dirs: test/cabal test/exe test/src bench/lib     include-dirs: include-    ghc-options: -threaded -Wall -Wno-name-shadowing -O0+    ghc-options: -threaded -Wall -Wno-name-shadowing -O0 -Wno-unticked-promoted-constructors     main-is: Main.hs     other-modules:         Development.IDE.Test@@ -405,7 +385,7 @@         extra,         filepath,         ghcide,-        lsp-test >= 0.12.0.0 && < 0.13,+        lsp-test == 0.13.0.0,         optparse-applicative,         process,         safe-exceptions,
session-loader/Development/IDE/Session.hs view
@@ -8,7 +8,6 @@ module Development.IDE.Session   (SessionLoadingOptions(..)   ,CacheDirs(..)-  ,defaultLoadingOptions   ,loadSession   ,loadSessionWithOptions   ,setInitialDynFlags@@ -34,6 +33,7 @@ import Data.Aeson import Data.Bifunctor import qualified Data.ByteString.Base16 as B16+import Data.Default import Data.Either.Extra import Data.Function import Data.Hashable@@ -60,9 +60,8 @@ 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 Language.LSP.Server+import Language.LSP.Types import System.Directory import qualified System.Directory.Extra as IO import System.FilePath@@ -99,23 +98,26 @@   --   return the path for storing generated GHC artifacts,   --   or 'Nothing' to respect the cradle setting   , getCacheDirs :: String -> [String] -> IO CacheDirs+  -- | Return the GHC lib dir to use for the 'unsafeGlobalDynFlags'+  , getInitialGhcLibDir :: IO (Maybe LibDir)   } -defaultLoadingOptions :: SessionLoadingOptions-defaultLoadingOptions = SessionLoadingOptions-    {findCradle = HieBios.findCradle-    ,loadCradle = HieBios.loadCradle-    ,getCacheDirs = getCacheDirsDefault-    }+instance Default SessionLoadingOptions where+    def = SessionLoadingOptions+        {findCradle = HieBios.findCradle+        ,loadCradle = HieBios.loadCradle+        ,getCacheDirs = getCacheDirsDefault+        ,getInitialGhcLibDir = getInitialGhcLibDirDefault+        } --- | Sets `unsafeGlobalDynFlags` on using the hie-bios cradle and returns the GHC libdir-setInitialDynFlags :: IO (Maybe LibDir)-setInitialDynFlags = do+getInitialGhcLibDirDefault :: IO (Maybe LibDir)+getInitialGhcLibDirDefault = do   dir <- IO.getCurrentDirectory   hieYaml <- runMaybeT $ yamlConfig dir-  cradle <- maybe (HieBios.loadImplicitCradle $ addTrailingPathSeparator dir) HieBios.loadCradle hieYaml+  cradle <- maybe (loadImplicitHieCradle $ addTrailingPathSeparator dir) HieBios.loadCradle hieYaml+  hPutStrLn stderr $ "setInitialDynFlags cradle: " ++ show cradle   libDirRes <- getRuntimeGhcLibDir cradle-  libdir <- case libDirRes of+  case libDirRes of       CradleSuccess libdir -> pure $ Just $ LibDir libdir       CradleFail err -> do         hPutStrLn stderr $ "Couldn't load cradle for libdir: " ++ show (err,dir,hieYaml,cradle)@@ -123,6 +125,11 @@       CradleNone -> do         hPutStrLn stderr $ "Couldn't load cradle (CradleNone)"         pure Nothing++-- | Sets `unsafeGlobalDynFlags` on using the hie-bios cradle and returns the GHC libdir+setInitialDynFlags :: SessionLoadingOptions -> IO (Maybe LibDir)+setInitialDynFlags SessionLoadingOptions{..} = do+  libdir <- getInitialGhcLibDir   dynFlags <- mapM dynFlagsForPrinting libdir   mapM_ setUnsafeGlobalDynFlags dynFlags   pure libdir@@ -177,7 +184,7 @@ -- components mapping to the same hie.yaml file are mapped to the same -- HscEnv which is updated as new components are discovered. loadSession :: FilePath -> IO (Action IdeGhcSession)-loadSession = loadSessionWithOptions defaultLoadingOptions+loadSession = loadSessionWithOptions def  loadSessionWithOptions :: SessionLoadingOptions -> FilePath -> IO (Action IdeGhcSession) loadSessionWithOptions SessionLoadingOptions{..} dir = do@@ -208,12 +215,11 @@   runningCradle <- newVar dummyAs :: IO (Var (Async (IdeResult HscEnvEq,[FilePath])))    return $ do-    extras@ShakeExtras{logger, eventer, restartShakeSession,-                       withIndefiniteProgress, ideNc, knownTargetsVar+    extras@ShakeExtras{logger, restartShakeSession, ideNc, knownTargetsVar, lspEnv                       } <- getShakeExtras      IdeOptions{ optTesting = IdeTesting optTesting-              , optCheckProject = checkProject+              , optCheckProject = getCheckProject               , optCustomDynFlags               , optExtensions               } <- getIdeOptions@@ -358,6 +364,7 @@           restartShakeSession []            -- Typecheck all files in the project on startup+          checkProject <- getCheckProject           unless (null cs || not checkProject) $ do                 cfps' <- liftIO $ filterM (IO.doesFileExist . fromNormalizedFilePath) (concatMap targetLocations cs)                 void $ shakeEnqueue extras $ mkDelayedAction "InitialLoad" Debug $ void $ do@@ -376,17 +383,19 @@            lfp <- flip makeRelative cfp <$> getCurrentDirectory            logInfo logger $ T.pack ("Consulting the cradle for " <> show lfp) -           when (isNothing hieYaml) $ eventer $ notifyUserImplicitCradle lfp+           when (isNothing hieYaml) $ mRunLspT lspEnv $+             sendNotification SWindowShowMessage $ notifyUserImplicitCradle lfp             cradle <- maybe (loadImplicitHieCradle $ addTrailingPathSeparator dir) loadCradle hieYaml -           when optTesting $ eventer $ notifyCradleLoaded lfp+           when optTesting $ mRunLspT lspEnv $+            sendNotification (SCustomMethod "ghcide/cradle/loaded") (toJSON cfp)             -- 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+           eopts <- mRunLspTCallback lspEnv (withIndefiniteProgress progMsg NotCancellable) $+              cradleToOptsAndLibDir cradle cfp             logDebug logger $ T.pack ("Session loading result: " <> show eopts)            case eopts of@@ -612,7 +621,7 @@ -- For the exact reason, see Note [Avoiding bad interface files]. setCacheDirs :: MonadIO m => Logger -> CacheDirs -> DynFlags -> m DynFlags setCacheDirs logger CacheDirs{..} dflags = do-    liftIO $ logInfo logger $ "Using interface files cache dir: " <> T.pack cacheDir+    liftIO $ logInfo logger $ "Using interface files cache dir: " <> T.pack (fromMaybe cacheDir hiCacheDir)     pure $ dflags           & maybe id setHiDir hiCacheDir           & maybe id setHieDir hieCacheDir@@ -796,24 +805,12 @@ cacheDir :: String cacheDir = "ghcide" -notifyUserImplicitCradle:: FilePath -> FromServerMessage-notifyUserImplicitCradle fp =-    NotShowMessage $-    NotificationMessage "2.0" WindowShowMessage $ ShowMessageParams MtInfo $-      "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).\n" <>-      "You should ignore this message, unless you see a 'Multi Cradle: No prefixes matched' error."--notifyCradleLoaded :: FilePath -> FromServerMessage-notifyCradleLoaded fp =-    NotCustomServer $-    NotificationMessage "2.0" (CustomServerMethod cradleLoadedMethod) $-    toJSON fp--cradleLoadedMethod :: T.Text-cradleLoadedMethod = "ghcide/cradle/loaded"-+notifyUserImplicitCradle:: FilePath -> ShowMessageParams+notifyUserImplicitCradle fp =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).\n"<>+  "You should ignore this message, unless you see a 'Multi Cradle: No prefixes matched' error." ----------------------------------------------------------------------------------------------------  data PackageSetupException
src/Development/IDE/Core/Compile.hs view
@@ -48,7 +48,7 @@  import HieDb -import Language.Haskell.LSP.Types (DiagnosticTag(..))+import Language.LSP.Types (DiagnosticTag(..))  import LoadIface (loadModuleInterface) import DriverPhases@@ -106,8 +106,8 @@ import Maybes (orElse)  import qualified Data.HashMap.Strict as HashMap-import qualified Language.Haskell.LSP.Messages as LSP-import qualified Language.Haskell.LSP.Types as LSP+import qualified Language.LSP.Types as LSP+import qualified Language.LSP.Server as LSP import Control.Concurrent.STM hiding (orElse) import Control.Concurrent.Extra import Data.Functor@@ -521,9 +521,9 @@             -- If the hash in the pending list doesn't match the current hash, then skip             Just pendingHash -> pendingHash /= hash         unless newerScheduled $ do-          tok <- pre+          pre           addRefsFromLoaded db targetPath (RealFile $ fromNormalizedFilePath srcPath) hash hf-          post tok+          post   where     mod_location    = ms_location mod_summary     targetPath      = Compat.ml_hie_file mod_location@@ -531,46 +531,42 @@      -- Get a progress token to report progress and update it for the current file     pre = do-      tok <- modifyVar indexProgressToken $ \case-        x@(Just tok) -> pure (x, tok)+      tok <- modifyVar indexProgressToken $ fmap dupe . \case+        x@(Just _) -> pure x         -- Create a token if we don't already have one         Nothing -> do-          u <- LSP.ProgressTextToken . T.pack . show . hashUnique <$> newUnique-          lspId <- getLspId se-          eventer se $ LSP.ReqWorkDoneProgressCreate $-            LSP.fmServerWorkDoneProgressCreateRequest lspId $-              LSP.WorkDoneProgressCreateParams { _token = u }-          eventer se $ LSP.NotWorkDoneProgressBegin $-            LSP.fmServerWorkDoneProgressBeginNotification-              LSP.ProgressParams-                  { _token = u-                  , _value = LSP.WorkDoneProgressBeginParams-                    { _title = "Indexing references from:"-                    , _cancellable = Nothing-                    , _message = Nothing-                    , _percentage = Nothing-                    }+          case lspEnv se of+            Nothing -> pure Nothing+            Just env -> LSP.runLspT env $ do+              u <- LSP.ProgressTextToken . T.pack . show . hashUnique <$> liftIO newUnique+              -- TODO: Wait for the progress create response to use the token+              _ <- LSP.sendRequest LSP.SWindowWorkDoneProgressCreate (LSP.WorkDoneProgressCreateParams u) (const $ pure ())+              LSP.sendNotification LSP.SProgress $ LSP.ProgressParams u $+                LSP.Begin $ LSP.WorkDoneProgressBeginParams+                  { _title = "Indexing references from:"+                  , _cancellable = Nothing+                  , _message = Nothing+                  , _percentage = Nothing                   }-          pure (Just u, u)+              pure (Just u)+       (!done, !remaining) <- atomically $ do         done <- readTVar indexCompleted         remaining <- HashMap.size <$> readTVar indexPending         pure (done, remaining)+       let progress = " (" <> T.pack (show done) <> "/" <> T.pack (show $ done + remaining) <> ")..."-      eventer se $ LSP.NotWorkDoneProgressReport $-        LSP.fmServerWorkDoneProgressReportNotification-          LSP.ProgressParams-              { _token = tok-              , _value = LSP.WorkDoneProgressReportParams-                { _cancellable = Nothing-                , _message = Just $ T.pack (show srcPath) <> progress-                , _percentage = Nothing-                }-              }-      pure tok +      whenJust (lspEnv se) $ \env -> whenJust tok $ \tok -> LSP.runLspT env $+        LSP.sendNotification LSP.SProgress $ LSP.ProgressParams tok $+          LSP.Report $ LSP.WorkDoneProgressReportParams+            { _cancellable = Nothing+            , _message = Just $ T.pack (show srcPath) <> progress+            , _percentage = Nothing+            }+     -- Report the progress once we are done indexing this file-    post tok = do+    post = do       mdone <- atomically $ do         -- Remove current element from pending         pending <- stateTVar indexPending $@@ -579,23 +575,20 @@         -- If we are done, report and reset completed         whenMaybe (HashMap.null pending) $           swapTVar indexCompleted 0-      when (coerce $ ideTesting se) $-        eventer se $ LSP.NotCustomServer $-          LSP.NotificationMessage "2.0" (LSP.CustomServerMethod "ghcide/reference/ready") (toJSON $ fromNormalizedFilePath srcPath)-      case mdone of-        Nothing -> pure ()-        Just done ->-          modifyVar_ indexProgressToken $ \_ -> do-            eventer se $ LSP.NotWorkDoneProgressEnd $-              LSP.fmServerWorkDoneProgressEndNotification-                LSP.ProgressParams-                    { _token = tok-                    , _value = LSP.WorkDoneProgressEndParams-                      { _message = Just $ "Finished indexing " <> T.pack (show done) <> " files"-                      }-                    }-            -- We are done with the current indexing cycle, so destroy the token-            pure Nothing+      whenJust (lspEnv se) $ \env -> LSP.runLspT env $+        when (coerce $ ideTesting se) $+          LSP.sendNotification (LSP.SCustomMethod "ghcide/reference/ready") $+            toJSON $ fromNormalizedFilePath srcPath+      whenJust mdone $ \done ->+        modifyVar_ indexProgressToken $ \tok -> do+          whenJust (lspEnv se) $ \env -> LSP.runLspT env $+            whenJust tok $ \tok ->+              LSP.sendNotification LSP.SProgress $ LSP.ProgressParams tok $+                LSP.End $ LSP.WorkDoneProgressEndParams+                  { _message = Just $ "Finished indexing " <> T.pack (show done) <> " files"+                  }+          -- We are done with the current indexing cycle, so destroy the token+          pure Nothing  writeAndIndexHieFile :: HscEnv -> ShakeExtras -> ModSummary -> NormalizedFilePath -> [GHC.AvailInfo] -> HieASTs Type -> BS.ByteString -> IO [FileDiagnostic] writeAndIndexHieFile hscEnv se mod_summary srcPath exports ast source =
src/Development/IDE/Core/FileExists.hs view
@@ -26,7 +26,9 @@ import           Development.Shake import           Development.Shake.Classes import           GHC.Generics-import           Language.Haskell.LSP.Types.Capabilities+import           Language.LSP.Server hiding (getVirtualFile)+import           Language.LSP.Types+import           Language.LSP.Types.Capabilities import qualified System.Directory as Dir import qualified System.FilePath.Glob as Glob @@ -148,8 +150,18 @@ -- | Installs the 'getFileExists' rules. --   Provides a fast implementation if client supports dynamic watched files. --   Creates a global state as a side effect in that case.-fileExistsRules :: ClientCapabilities -> VFSHandle -> Rules ()-fileExistsRules ClientCapabilities{_workspace} vfs = do+fileExistsRules :: Maybe (LanguageContextEnv c) -> VFSHandle -> Rules ()+fileExistsRules lspEnv vfs = do+  supportsWatchedFiles <- case lspEnv of+    Just lspEnv' -> liftIO $ runLspT lspEnv' $ do+      ClientCapabilities {_workspace} <- getClientCapabilities+      case () of+        _ | Just WorkspaceClientCapabilities{_didChangeWatchedFiles} <- _workspace+          , Just DidChangeWatchedFilesClientCapabilities{_dynamicRegistration} <- _didChangeWatchedFiles+          , Just True <- _dynamicRegistration+          -> pure True+        _ -> pure False+    Nothing -> pure False   -- Create the global always, although it should only be used if we have fast rules.   -- But there's a chance someone will send unexpected notifications anyway,   -- e.g. https://github.com/haskell/ghcide/issues/599@@ -159,12 +171,9 @@   opts <- liftIO $ getIdeOptionsIO extras   let globs = watchedGlobs opts -  case () of-    _ | Just WorkspaceClientCapabilities{_didChangeWatchedFiles} <- _workspace-      , Just DidChangeWatchedFilesClientCapabilities{_dynamicRegistration} <- _didChangeWatchedFiles-      , Just True <- _dynamicRegistration-        -> fileExistsRulesFast globs vfs-      | otherwise -> fileExistsRulesSlow vfs+  if supportsWatchedFiles+    then fileExistsRulesFast globs vfs+    else fileExistsRulesSlow vfs  -- Requires an lsp client that provides WatchedFiles notifications, but assumes that this has already been checked. fileExistsRulesFast :: [String] -> VFSHandle -> Rules ()
src/Development/IDE/Core/FileStore.hs view
@@ -59,8 +59,9 @@  import qualified Development.IDE.Types.Logger as L -import Language.Haskell.LSP.Core-import Language.Haskell.LSP.VFS+import Language.LSP.Server hiding (getVirtualFile)+import qualified Language.LSP.Server as LSP+import Language.LSP.VFS  makeVFSHandle :: IO VFSHandle makeVFSHandle = do@@ -77,9 +78,9 @@                     Just content -> Map.insert uri (VirtualFile nextVersion 0 (Rope.fromText content)) vfs         } -makeLSPVFSHandle :: LspFuncs c -> VFSHandle-makeLSPVFSHandle lspFuncs = VFSHandle-    { getVirtualFile = getVirtualFileFunc lspFuncs+makeLSPVFSHandle :: LanguageContextEnv c -> VFSHandle+makeLSPVFSHandle lspEnv = VFSHandle+    { getVirtualFile = runLspT lspEnv . LSP.getVirtualFile     , setVirtualFileContents = Nothing    } @@ -179,7 +180,7 @@       Nothing -> do         foi <- use_ IsFileOfInterest f         liftIO $ case foi of-          IsFOI Modified -> getCurrentTime+          IsFOI Modified{} -> getCurrentTime           _ -> do             (large,small) <- getModTime $ fromNormalizedFilePath f             pure $ internalTimeToUTCTime large small@@ -200,7 +201,8 @@                 -> IO () setFileModified state saved nfp = do     ideOptions <- getIdeOptionsIO $ shakeExtras state-    let checkParents = case optCheckParents ideOptions of+    doCheckParents <- optCheckParents ideOptions+    let checkParents = case doCheckParents of           AlwaysCheck -> True           CheckOnSaveAndClose -> saved           _ -> False
src/Development/IDE/Core/IdeConfiguration.hs view
@@ -21,7 +21,7 @@ import           Development.IDE.Core.Shake import           Development.IDE.Types.Location import           Development.Shake-import           Language.Haskell.LSP.Types+import           Language.LSP.Types import           System.FilePath (isRelative)  -- | Lsp client relevant configuration details
src/Development/IDE/Core/OfInterest.hs view
@@ -104,7 +104,8 @@      -- Update the exports map for non FOIs     -- We can skip this if checkProject is True, assuming they never change under our feet.-    IdeOptions{ optCheckProject = checkProject } <- getIdeOptions+    IdeOptions{ optCheckProject = doCheckProject } <- getIdeOptions+    checkProject <- liftIO doCheckProject     ifaces <- if checkProject then return Nothing else runMaybeT $ do         deps <- MaybeT $ sequence <$> uses GetDependencies files         hiResults <- lift $ uses GetModIface (nubOrd $ foldMap transitiveModuleDeps deps)
src/Development/IDE/Core/PositionMapping.hs view
@@ -24,7 +24,7 @@  import Control.Monad import qualified Data.Text as T-import Language.Haskell.LSP.Types+import Language.LSP.Types import Data.List import Data.Algorithm.Diff import Data.Bifunctor
src/Development/IDE/Core/RuleTypes.hs view
@@ -26,19 +26,17 @@ import Development.IDE.Types.KnownTargets import           Data.Hashable import           Data.Typeable-import qualified Data.Set as S import qualified Data.Map as M import           Development.Shake import           GHC.Generics                             (Generic) -import Module (InstalledUnitId) import HscTypes (ModGuts, hm_iface, HomeModInfo, hm_linkable)  import           Development.IDE.Spans.Common import           Development.IDE.Spans.LocalBindings import           Development.IDE.Import.FindImports (ArtifactsLocation) import Data.ByteString (ByteString)-import Language.Haskell.LSP.Types (NormalizedFilePath)+import Language.LSP.Types (NormalizedFilePath) import TcRnMonad (TcGblEnv) import qualified Data.ByteString.Char8 as BS import Development.IDE.Types.Options (IdeGhcSession)@@ -223,9 +221,8 @@ -- | A GHC session preloaded with all the dependencies type instance RuleResult GhcSessionDeps = HscEnvEq --- | Resolve the imports in a module to the file path of a module--- in the same package or the package id of another package.-type instance RuleResult GetLocatedImports = ([(Located ModuleName, Maybe ArtifactsLocation)], S.Set InstalledUnitId)+-- | Resolve the imports in a module to the file path of a module in the same package+type instance RuleResult GetLocatedImports = [(Located ModuleName, Maybe ArtifactsLocation)]  -- | This rule is used to report import cycles. It depends on GetDependencyInformation. -- We cannot report the cycles directly from GetDependencyInformation since@@ -296,7 +293,10 @@ instance Binary   GetFileContents  -data FileOfInterestStatus = OnDisk | Modified+data FileOfInterestStatus+  = OnDisk+  | Modified { firstOpen :: !Bool -- ^ was this file just opened+             }   deriving (Eq, Show, Typeable, Generic) instance Hashable FileOfInterestStatus instance NFData   FileOfInterestStatus
src/Development/IDE/Core/Rules.hs view
@@ -57,14 +57,17 @@     getBindingsRule,     needsCompilationRule,     generateCoreRule,-    getImportMapRule+    getImportMapRule,+    regenerateHiFile,+    ghcSessionDepsDefinition,+    getParsedModuleDefinition,+    typeCheckRuleDefinition,     ) where  import Fingerprint -import Data.Aeson (fromJSON,toJSON, Result(Success), FromJSON)+import Data.Aeson (toJSON, Result(Success)) import Data.Binary hiding (get, put)-import Data.Default import Data.Tuple.Extra import Control.Monad.Extra import Control.Monad.Trans.Class@@ -83,7 +86,6 @@ import Development.IDE.GHC.Compat hiding (parseModule, typecheckModule, writeHieFile, TargetModule, TargetFile) import Development.IDE.GHC.ExactPrint import Development.IDE.GHC.Util-import Data.Either.Extra import qualified Development.IDE.Types.Logger as L import Data.Maybe import           Data.Foldable@@ -99,10 +101,9 @@ import Development.IDE.Core.RuleTypes import qualified Data.ByteString.Char8 as BS import Development.IDE.Core.PositionMapping-import           Language.Haskell.LSP.Types (DocumentHighlight (..), SymbolInformation(..))-import Language.Haskell.LSP.VFS-import qualified Language.Haskell.LSP.Messages as LSP-import qualified Language.Haskell.LSP.Types as LSP+import           Language.LSP.Types (DocumentHighlight (..), SymbolInformation(..), SMethod(SCustomMethod))+import qualified Language.LSP.Server as LSP+import Language.LSP.VFS  import qualified GHC.LanguageExtensions as LangExt import HscTypes hiding (TargetModule, TargetFile)@@ -136,6 +137,8 @@ import Data.ByteString.Encoding as T  import qualified HieDb+import Ide.Plugin.Config+import qualified Data.Aeson.Types as A  -- | 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@@ -402,17 +405,11 @@         (diags, imports') <- fmap unzip $ forM imports $ \(isSource, (mbPkgName, modName)) -> do             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))-                Right (PackageImport pkgId) -> liftIO $ do-                    diagsOrPkgDeps <- computePackageDeps env pkgId-                    case diagsOrPkgDeps of-                        Left diags -> pure (diags, Right Nothing)-                        Right pkgIds -> pure ([], Right $ Just $ pkgId : pkgIds)-        let (moduleImports, pkgImports) = partitionEithers imports'-        case sequence pkgImports of-            Nothing -> pure (concat diags, Nothing)-            Just pkgImports -> pure (concat diags, Just (moduleImports, Set.fromList $ concat pkgImports))+                Left diags -> pure (diags, Just (modName, Nothing))+                Right (FileImport path) -> pure ([], Just (modName, Just path))+                Right PackageImport -> pure ([], Nothing)+        let moduleImports = catMaybes imports'+        pure (concat diags, Just moduleImports)  type RawDepM a = StateT (RawDependencyInformation, IntMap ArtifactsLocation) Action a @@ -427,19 +424,23 @@ -- imports recursively. rawDependencyInformation :: [NormalizedFilePath] -> Action RawDependencyInformation rawDependencyInformation fs = do-    (rdi, ss) <- execRawDepM (mapM_ go fs)+    (rdi, ss) <- execRawDepM (goPlural fs)     let bm = IntMap.foldrWithKey (updateBootMap rdi) IntMap.empty ss     return (rdi { rawBootMap = bm })   where+    goPlural ff = do+        mss <- lift $ (fmap.fmap) fst <$> uses GetModSummaryWithoutTimestamps ff+        zipWithM go ff mss+     go :: NormalizedFilePath -- ^ Current module being processed+       -> Maybe ModSummary   -- ^ ModSummary of the module        -> StateT (RawDependencyInformation, IntMap ArtifactsLocation) Action FilePathId-    go f = do+    go f msum = do       -- First check to see if we have already processed the FilePath       -- If we have, just return its Id but don't update any of the state.       -- Otherwise, we need to process its imports.       checkAlreadyProcessed f $ do-          msum <- lift $ fmap fst <$> use GetModSummaryWithoutTimestamps f-          let al =  modSummaryToArtifactsLocation f msum+          let al = modSummaryToArtifactsLocation f msum           -- Get a fresh FilePathId for the new file           fId <- getFreshFid al           -- Adding an edge to the bootmap so we can make sure to@@ -454,19 +455,19 @@             -- elements in the queue               modifyRawDepInfo (insertImport fId (Left ModuleParseError))               return fId-            Just (modImports, pkgImports) -> do+            Just modImports -> do               -- Get NFPs of the imports which have corresponding files               -- Imports either come locally from a file or from a package.               let (no_file, with_file) = splitImports modImports                   (mns, ls) = unzip with_file               -- Recursively process all the imports we just learnt about               -- and get back a list of their FilePathIds-              fids <- mapM (go . artifactFilePath) ls+              fids <- goPlural $ map artifactFilePath ls               -- Associate together the ModuleName with the FilePathId               let moduleImports' = map (,Nothing) no_file ++ zip mns (map Just fids)               -- Insert into the map the information about this modules               -- imports.-              modifyRawDepInfo $ insertImport fId (Right $ ModuleImports moduleImports' pkgImports)+              modifyRawDepInfo $ insertImport fId (Right $ ModuleImports moduleImports')               return fId  @@ -593,10 +594,10 @@    isFoi <- use_ IsFileOfInterest f   diagsWrite <- case isFoi of-    IsFOI Modified -> do-      when (coerce $ ideTesting se) $-        liftIO $ eventer se $ LSP.NotCustomServer $-          LSP.NotificationMessage "2.0" (LSP.CustomServerMethod "ghcide/reference/ready") (toJSON $ fromNormalizedFilePath f)+    IsFOI Modified{firstOpen = False} -> do+      when (coerce $ ideTesting se) $ liftIO $ mRunLspT (lspEnv se) $+        LSP.sendNotification (SCustomMethod "ghcide/reference/ready") $+          toJSON $ fromNormalizedFilePath f       pure []     _ | Just asts <- masts -> do           source <- getSourceFileSource f@@ -612,7 +613,7 @@ getImportMapRule :: Rules () getImportMapRule = define $ \GetImportMap f -> do   im <- use GetLocatedImports f-  let mkImports (fileImports, _) = M.fromList $ mapMaybe (\(m, mfp) -> (unLoc m,) . artifactFilePath <$> mfp) fileImports+  let mkImports fileImports = M.fromList $ mapMaybe (\(m, mfp) -> (unLoc m,) . artifactFilePath <$> mfp) fileImports   pure ([], ImportMap . mkImports <$> im)  -- | Ensure that go to definition doesn't block on startup@@ -636,17 +637,7 @@       (hscEnv -> hsc, _)        <- useWithStale_ GhcSessionDeps file       (HAR{refMap=rf}, _)       <- useWithStale_ GetHieAst file --- When possible, rely on the haddocks embedded in our interface files--- This creates problems on ghc-lib, see comment on 'getDocumentationTryGhc'-#if !defined(GHC_LIB)-      let parsedDeps = []-#else-      deps <- fromMaybe (TransitiveDependencies [] [] []) <$> use GetDependencies file-      let tdeps = transitiveModuleDeps deps-      parsedDeps <- uses_ GetParsedModule tdeps-#endif--      dkMap <- liftIO $ mkDocMap hsc parsedDeps rf tc+      dkMap <- liftIO $ mkDocMap hsc rf tc       return ([],Just dkMap)  -- | Persistent rule to ensure that hover doesn't block on startup@@ -665,7 +656,7 @@ readHieFileFromDisk :: FilePath -> ExceptT SomeException IdeAction HieFile readHieFileFromDisk hie_loc = do   nc <- asks ideNc-  log <- asks $ L.logInfo . logger+  log <- asks $ L.logDebug . logger   res <- liftIO $ tryAny $ loadHieFile (mkUpdater nc) hie_loc   liftIO . log $ either (const $ "FAILED LOADING HIE FILE FOR:" <> T.pack (show hie_loc))                         (const $ "SUCCEEDED LOADING HIE FILE FOR:" <> T.pack (show hie_loc))@@ -826,9 +817,9 @@       | hash == HieDb.modInfoHash (HieDb.hieModInfo row)       , hie_loc == HieDb.hieModuleHieFile row  -> do       -- All good, the db has indexed the file-      when (coerce $ ideTesting se) $-        liftIO $ eventer se $ LSP.NotCustomServer $-          LSP.NotificationMessage "2.0" (LSP.CustomServerMethod "ghcide/reference/ready") (toJSON $ fromNormalizedFilePath f)+      when (coerce $ ideTesting se) $ liftIO $ mRunLspT (lspEnv se) $+        LSP.sendNotification (SCustomMethod "ghcide/reference/ready") $+          toJSON $ fromNormalizedFilePath f     -- Not in db, must re-index     _ -> do       ehf <- liftIO $ runIdeAction "GetModIfaceFromDiskAndIndex" se $ runExceptT $@@ -838,7 +829,7 @@         Left err -> fail $ "failed to read .hie file " ++ show hie_loc ++ ": " ++ displayException err         -- can just re-index the file we read from disk         Right hf -> liftIO $ do-          L.logInfo (logger se) $ "Re-indexing hie file for" <> T.pack (show f)+          L.logDebug (logger se) $ "Re-indexing hie file for" <> T.pack (show f)           indexHieFile se ms f hash hf    let fp = hiFileFingerPrint x@@ -857,7 +848,7 @@             if modificationTime x < modificationTime modVersion                 then pure SourceModified                 else do-                    (fileImports, _) <- use_ GetLocatedImports f+                    fileImports <- use_ GetLocatedImports f                     let imports = fmap artifactFilePath . snd <$> fileImports                     deps <- uses_ IsHiFileStable (catMaybes imports)                     pure $ if all (== SourceUnmodifiedAndStable) deps@@ -924,7 +915,6 @@  getModIfaceRule :: Rules () getModIfaceRule = defineEarlyCutoff $ \GetModIface f -> do-#if !defined(GHC_LIB)   fileOfInterest <- use_ IsFileOfInterest f   res@(_,(_,mhmi)) <- case fileOfInterest of     IsFOI status -> do@@ -951,13 +941,6 @@       compiledLinkables <- getCompiledLinkables <$> getIdeGlobalAction       liftIO $ modifyVar_ compiledLinkables $ \old -> pure $ extendModuleEnv old mod time   pure res-#else-    tm <- use_ TypeCheck f-    hsc <- hscEnv <$> use_ GhcSessionDeps f-    (diags, !hiFile) <- liftIO $ compileToObjCodeIfNeeded hsc Nothing (error "can't compile with ghc-lib") tm-    let fp = hiFileFingerPrint <$> hiFile-    return (fp, (diags, hiFile))-#endif  getModIfaceWithoutLinkableRule :: Rules () getModIfaceWithoutLinkableRule = defineEarlyCutoff $ \GetModIfaceWithoutLinkable f -> do@@ -1047,12 +1030,13 @@  -- | Returns the client configurarion stored in the IdeState. -- You can use this function to access it from shake Rules-getClientConfigAction :: (Default a, FromJSON a) => Action a-getClientConfigAction = do+getClientConfigAction :: Config -- ^ default value+                      -> Action Config+getClientConfigAction defValue = do   mbVal <- unhashed <$> useNoFile_ GetClientSettings-  case fromJSON <$> mbVal of+  case A.parse (parseConfig defValue) <$> mbVal of     Just (Success c) -> return c-    _ -> return def+    _ -> return defValue  -- | For now we always use bytecode getLinkableType :: NormalizedFilePath -> Action (Maybe LinkableType)
src/Development/IDE/Core/Service.hs view
@@ -25,9 +25,9 @@ import           Development.IDE.Core.OfInterest import Development.IDE.Types.Logger as Logger import           Development.Shake-import qualified Language.Haskell.LSP.Messages as LSP-import qualified Language.Haskell.LSP.Types as LSP-import qualified Language.Haskell.LSP.Types.Capabilities as LSP+import qualified Language.LSP.Server as LSP+import qualified Language.LSP.Types as LSP+import           Ide.Plugin.Config  import           Development.IDE.Core.Shake import Control.Monad@@ -37,12 +37,8 @@ -- Exposed API  -- | Initialise the Compiler Service.-initialise :: LSP.ClientCapabilities-           -> Rules ()-           -> IO LSP.LspId-           -> (LSP.FromServerMessage -> IO ())-           -> WithProgressFunc-           -> WithIndefiniteProgressFunc+initialise :: Rules ()+           -> Maybe (LSP.LanguageContextEnv Config)            -> Logger            -> Debouncer LSP.NormalizedUri            -> IdeOptions@@ -50,13 +46,9 @@            -> HieDb            -> IndexQueue            -> IO IdeState-initialise caps mainRule getLspId toDiags wProg wIndefProg logger debouncer options vfs hiedb hiedbChan =+initialise mainRule lspEnv logger debouncer options vfs hiedb hiedbChan =     shakeOpen-        getLspId-        toDiags-        wProg-        wIndefProg-        caps+        lspEnv         logger         debouncer         (optShakeProfiling options)@@ -70,7 +62,7 @@             addIdeGlobal $ GlobalIdeOptions options             fileStoreRules vfs             ofInterestRules-            fileExistsRules caps vfs+            fileExistsRules lspEnv vfs             mainRule  writeProfile :: IdeState -> FilePath -> IO ()
src/Development/IDE/Core/Shake.hs view
@@ -7,6 +7,7 @@ {-# LANGUAGE RecursiveDo #-} {-# LANGUAGE TypeFamilies               #-} {-# LANGUAGE ConstraintKinds            #-}+{-# LANGUAGE PolyKinds #-}  -- | A Shake implementation of the compiler service. --@@ -39,6 +40,7 @@     BadDependency(..),     define, defineEarlyCutoff, defineOnDisk, needOnDisk, needOnDisks,     getDiagnostics,+    mRunLspT, mRunLspTCallback,     getHiddenDiagnostics,     IsIdeGlobal, addIdeGlobal, addIdeGlobalExtras, getIdeGlobalState, getIdeGlobalAction,     getIdeGlobalExtras,@@ -48,7 +50,6 @@     garbageCollect,     knownTargets,     setPriority,-    sendEvent,     ideLogger,     actionLogger,     FileVersion(..),@@ -97,7 +98,7 @@ import Development.IDE.Types.KnownTargets import Development.IDE.Types.Shake import qualified Development.IDE.Types.Logger as Logger-import Language.Haskell.LSP.Diagnostics+import Language.LSP.Diagnostics import qualified Data.SortedList as SL import           Development.IDE.Types.Diagnostics import Development.IDE.Types.Exports@@ -107,19 +108,16 @@ import           Control.Concurrent.Extra import           Control.Concurrent.STM import           Control.DeepSeq-import           Control.Exception.Extra import           System.Time.Extra import           Data.Typeable-import qualified Language.Haskell.LSP.Core as LSP-import qualified Language.Haskell.LSP.Messages as LSP-import qualified Language.Haskell.LSP.Types as LSP+import qualified Language.LSP.Server as LSP+import qualified Language.LSP.Types as LSP import           System.FilePath hiding (makeRelative) import qualified Development.Shake as Shake import           Control.Monad.Extra import           Data.Time import           GHC.Generics-import           System.IO.Unsafe-import Language.Haskell.LSP.Types+import Language.LSP.Types import qualified Control.Monad.STM as STM import Control.Monad.IO.Class import Control.Monad.Reader@@ -127,17 +125,21 @@ import Data.Traversable import Data.Hashable import Development.IDE.Core.Tracing-import Language.Haskell.LSP.VFS+import Language.LSP.VFS  import Data.IORef import NameCache import UniqSupply import PrelInfo-import Language.Haskell.LSP.Types.Capabilities+import Language.LSP.Types.Capabilities import OpenTelemetry.Eventlog import GHC.Fingerprint  import HieDb.Types+import           Control.Exception.Extra hiding (bracket_)+import UnliftIO.Exception (bracket_)+import           Ide.Plugin.Config+import Data.Default  -- | We need to serialize writes to the database, so we send any function that -- needs to write to the database over the channel, where it will be picked up by@@ -156,7 +158,8 @@  -- information we stash inside the shakeExtra field data ShakeExtras = ShakeExtras-    {eventer :: LSP.FromServerMessage -> IO ()+    { --eventer :: LSP.FromServerMessage -> IO ()+     lspEnv :: Maybe (LSP.LanguageContextEnv Config)     ,debouncer :: Debouncer NormalizedUri     ,logger :: Logger     ,globals :: Var (HMap.HashMap TypeRep Dynamic)@@ -174,15 +177,10 @@     ,inProgress :: Var (HMap.HashMap NormalizedFilePath Int)     -- ^ How many rules are running for each file     ,progressUpdate :: ProgressEvent -> IO ()-    -- ^ The generator for unique Lsp identifiers     ,ideTesting :: IdeTesting     -- ^ Whether to enable additional lsp messages used by the test suite for checking invariants     ,session :: MVar ShakeSession     -- ^ Used in the GhcSession rule to forcefully restart the session after adding a new component-    ,withProgress           :: WithProgressFunc-    -- ^ Report progress about some long running operation (on top of the progress shown by 'lspShakeProgress')-    ,withIndefiniteProgress :: WithIndefiniteProgressFunc-    -- ^ Same as 'withProgress', but for processes that do not report the percentage complete     ,restartShakeSession :: [DelayedAction ()] -> IO ()     ,ideNc :: IORef NameCache     -- | A mapping of module name to known target (or candidate targets, if missing)@@ -197,12 +195,11 @@     , persistentKeys :: Var (HMap.HashMap Key GetStalePersistent)       -- ^ Registery for functions that compute/get "stale" results for the rule       -- (possibly from disk)-    , getLspId :: IO LspId     , vfs :: VFSHandle     }  type WithProgressFunc = forall a.-    T.Text -> LSP.ProgressCancellable -> ((LSP.Progress -> IO ()) -> IO a) -> IO a+    T.Text -> LSP.ProgressCancellable -> ((LSP.ProgressAmount -> IO ()) -> IO a) -> IO a type WithIndefiniteProgressFunc = forall a.     T.Text -> LSP.ProgressCancellable -> IO a -> IO a @@ -373,29 +370,24 @@     ,shakeSession    :: MVar ShakeSession     ,shakeClose      :: IO ()     ,shakeExtras     :: ShakeExtras-    ,shakeProfileDir :: Maybe FilePath+    ,shakeDatabaseProfile :: ShakeDatabase -> IO (Maybe FilePath)     ,stopProgressReporting :: IO ()     }    -- This is debugging code that generates a series of profiles, if the Boolean is true-shakeDatabaseProfile :: Maybe FilePath -> ShakeDatabase -> IO (Maybe FilePath)-shakeDatabaseProfile mbProfileDir shakeDb =+shakeDatabaseProfileIO :: Maybe FilePath -> IO(ShakeDatabase -> IO (Maybe FilePath))+shakeDatabaseProfileIO mbProfileDir = do+    profileStartTime <- formatTime defaultTimeLocale "%Y%m%d-%H%M%S" <$> getCurrentTime+    profileCounter <- newVar (0::Int)+    return $ \shakeDb ->         for mbProfileDir $ \dir -> do                 count <- modifyVar profileCounter $ \x -> let !y = x+1 in return (y,y)                 let file = "ide-" ++ profileStartTime ++ "-" ++ takeEnd 5 ("0000" ++ show count) <.> "html"                 shakeProfileDatabase shakeDb $ dir </> file                 return (dir </> file) -{-# NOINLINE profileStartTime #-}-profileStartTime :: String-profileStartTime = unsafePerformIO $ formatTime defaultTimeLocale "%Y%m%d-%H%M%S" <$> getCurrentTime--{-# NOINLINE profileCounter #-}-profileCounter :: Var Int-profileCounter = unsafePerformIO $ newVar 0- setValues :: IdeRule k v           => Var Values           -> k@@ -452,11 +444,7 @@     Failed _ -> b  -- | Open a 'IdeState', should be shut using 'shakeShut'.-shakeOpen :: IO LSP.LspId-          -> (LSP.FromServerMessage -> IO ()) -- ^ diagnostic handler-          -> WithProgressFunc-          -> WithIndefiniteProgressFunc-          -> ClientCapabilities+shakeOpen :: Maybe (LSP.LanguageContextEnv Config)           -> Logger           -> Debouncer NormalizedUri           -> Maybe FilePath@@ -468,8 +456,9 @@           -> ShakeOptions           -> Rules ()           -> IO IdeState-shakeOpen getLspId eventer withProgress withIndefiniteProgress clientCapabilities logger debouncer+shakeOpen lspEnv logger debouncer   shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) hiedb indexQueue vfs opts rules = mdo+     inProgress <- newVar HMap.empty     us <- mkSplitUniqSupply 'r'     ideNc <- newIORef (initNameCache us knownKeyNames)@@ -497,6 +486,8 @@          actionQueue <- newQueue +        let clientCapabilities = maybe def LSP.resClientCapabilities lspEnv+         pure (ShakeExtras{..}, cancel progressAsync)     (shakeDbM, shakeClose) <-         shakeOpenDatabase@@ -505,6 +496,7 @@     shakeDb <- shakeDbM     initSession <- newSession shakeExtras shakeDb []     shakeSession <- newMVar initSession+    shakeDatabaseProfile <- shakeDatabaseProfileIO shakeProfileDir     let ideState = IdeState{..}      IdeOptions{ optOTMemoryProfiling = IdeOTMemoryProfiling otProfilingEnabled } <- getIdeOptionsIO shakeExtras@@ -526,7 +518,7 @@                     case v of                         KickCompleted -> STM.retry                         KickStarted -> return ()-                asyncReporter <- async lspShakeProgress+                asyncReporter <- async $ mRunLspT lspEnv lspShakeProgress                 progressLoopReporting asyncReporter             progressLoopReporting asyncReporter = do                 atomically $ do@@ -537,54 +529,55 @@                 cancel asyncReporter                 progressLoopIdle +            lspShakeProgress :: LSP.LspM config ()             lspShakeProgress = do                 -- first sleep a bit, so we only show progress messages if it's going to take                 -- a "noticable amount of time" (we often expect a thread kill to arrive before the sleep finishes)-                unless testing $ sleep 0.1-                lspId <- getLspId-                u <- ProgressTextToken . T.pack . show . hashUnique <$> newUnique-                eventer $ LSP.ReqWorkDoneProgressCreate $-                  LSP.fmServerWorkDoneProgressCreateRequest lspId $-                    LSP.WorkDoneProgressCreateParams { _token = u }-                bracket_ (start u) (stop u) (loop u Nothing)+                liftIO $ unless testing $ sleep 0.1+                u <- ProgressTextToken . T.pack . show . hashUnique <$> liftIO newUnique++                void $ LSP.sendRequest LSP.SWindowWorkDoneProgressCreate+                    LSP.WorkDoneProgressCreateParams { _token = u } $ const (pure ())++                bracket_+                  (start u)+                  (stop u)+                  (loop u Nothing)                 where-                    start id = eventer $ LSP.NotWorkDoneProgressBegin $-                      LSP.fmServerWorkDoneProgressBeginNotification+                    start id = LSP.sendNotification LSP.SProgress $                         LSP.ProgressParams                             { _token = id-                            , _value = WorkDoneProgressBeginParams+                            , _value = LSP.Begin $ WorkDoneProgressBeginParams                               { _title = "Processing"                               , _cancellable = Nothing                               , _message = Nothing                               , _percentage = Nothing                               }                             }-                    stop id = eventer $ LSP.NotWorkDoneProgressEnd $-                      LSP.fmServerWorkDoneProgressEndNotification+                    stop id = LSP.sendNotification LSP.SProgress                         LSP.ProgressParams                             { _token = id-                            , _value = WorkDoneProgressEndParams+                            , _value = LSP.End WorkDoneProgressEndParams                               { _message = Nothing                               }                             }                     sample = 0.1                     loop id prev = do-                        sleep sample-                        current <- readVar inProgress+                        liftIO $ sleep sample+                        current <- liftIO $ readVar inProgress                         let done = length $ filter (== 0) $ HMap.elems current                         let todo = HMap.size current                         let next = Just $ T.pack $ show done <> "/" <> show todo                         when (next /= prev) $-                            eventer $ LSP.NotWorkDoneProgressReport $-                              LSP.fmServerWorkDoneProgressReportNotification-                                LSP.ProgressParams-                                    { _token = id-                                    , _value = LSP.WorkDoneProgressReportParams-                                    { _cancellable = Nothing-                                    , _message = next-                                    , _percentage = Nothing-                                    }-                                    }+                          LSP.sendNotification LSP.SProgress $+                          LSP.ProgressParams+                              { _token = id+                              , _value = LSP.Report $ LSP.WorkDoneProgressReportParams+                                { _cancellable = Nothing+                                , _message = next+                                , _percentage = Nothing+                                }+                              }                         loop id next  shakeProfile :: IdeState -> FilePath -> IO ()@@ -630,7 +623,7 @@         shakeSession         (\runner -> do               (stopTime,()) <- duration (cancelShakeSession runner)-              res <- shakeDatabaseProfile shakeProfileDir shakeDb+              res <- shakeDatabaseProfile shakeDb               let profile = case res of                       Just fp -> ", profile saved at " <> fp                       _ -> ""@@ -648,9 +641,8 @@ notifyTestingLogMessage :: ShakeExtras -> T.Text -> IO () notifyTestingLogMessage extras msg = do     (IdeTesting isTestMode) <- optTesting <$> getIdeOptionsIO extras-    let notif = LSP.NotLogMessage $ LSP.NotificationMessage "2.0" LSP.WindowLogMessage-                                  $ LSP.LogMessageParams LSP.MtLog msg-    when isTestMode $ eventer extras notif+    let notif = LSP.LogMessageParams LSP.MtLog msg+    when isTestMode $ mRunLspT (lspEnv extras) $ LSP.sendNotification LSP.SWindowLogMessage notif   -- | Enqueue an action in the existing 'ShakeSession'.@@ -742,6 +734,18 @@       d' = DelayedAction (Just u) s p a'   return (b, d') +mRunLspT :: Applicative m => Maybe (LSP.LanguageContextEnv c ) -> LSP.LspT c m () -> m ()+mRunLspT (Just lspEnv) f = LSP.runLspT lspEnv f+mRunLspT Nothing _ = pure ()++mRunLspTCallback :: Monad m+                 => Maybe (LSP.LanguageContextEnv c)+                 -> (LSP.LspT c m a -> LSP.LspT c m a)+                 -> m a+                 -> m a+mRunLspTCallback (Just lspEnv) f g = LSP.runLspT lspEnv $ f (lift g)+mRunLspTCallback Nothing _ g = g+ getDiagnostics :: IdeState -> IO [FileDiagnostic] getDiagnostics IdeState{shakeExtras = ShakeExtras{diagnostics}} = do     val <- readVar diagnostics@@ -1027,7 +1031,7 @@   -> ShakeExtras   -> [(ShowDiagnostic,Diagnostic)] -- ^ current results   -> m ()-updateFileDiagnostics fp k ShakeExtras{diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, eventer} current = liftIO $ do+updateFileDiagnostics fp k ShakeExtras{logger, diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, lspEnv} current = liftIO $ do     modTime <- (currentValue . fst =<<) <$> getValues state GetModificationTime fp     let (currentShown, currentHidden) = partition ((== ShowDiag) . fst) current         uri = filePathToUri' fp@@ -1048,25 +1052,18 @@         registerEvent debouncer delay uri $ do              mask_ $ modifyVar_ publishedDiagnostics $ \published -> do                  let lastPublish = HMap.lookupDefault [] uri published-                 when (lastPublish /= newDiags) $-                     eventer $ publishDiagnosticsNotification (fromNormalizedUri uri) newDiags+                 when (lastPublish /= newDiags) $ case lspEnv of+                   Nothing -> -- Print an LSP event.+                     logInfo logger $ showDiagnosticsColored $ map (fp,ShowDiag,) newDiags+                   Just env -> LSP.runLspT env $+                     LSP.sendNotification LSP.STextDocumentPublishDiagnostics $+                       LSP.PublishDiagnosticsParams (fromNormalizedUri uri) ver (List newDiags)                  pure $! HMap.insert uri newDiags published -publishDiagnosticsNotification :: Uri -> [Diagnostic] -> LSP.FromServerMessage-publishDiagnosticsNotification uri diags =-    LSP.NotPublishDiagnostics $-    LSP.NotificationMessage "2.0" LSP.TextDocumentPublishDiagnostics $-    LSP.PublishDiagnosticsParams uri (List diags)- newtype Priority = Priority Double  setPriority :: Priority -> Action () setPriority (Priority p) = reschedule p--sendEvent :: LSP.FromServerMessage -> Action ()-sendEvent e = do-    ShakeExtras{eventer} <- getShakeExtras-    liftIO $ eventer e  ideLogger :: IdeState -> Logger ideLogger IdeState{shakeExtras=ShakeExtras{logger}} = logger
src/Development/IDE/Core/Tracing.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE DataKinds #-} #include "ghc-api-version.h" module Development.IDE.Core.Tracing     ( otTracedHandler@@ -28,11 +27,11 @@                                                  GhcSessionIO (GhcSessionIO)) import           Development.IDE.Types.Logger   (logInfo, Logger, logDebug) import           Development.IDE.Types.Shake    (ValueWithDiagnostics(..), Key (..), Value, Values)-import           Development.Shake              (Action, actionBracket, liftIO)+import           Development.Shake              (Action, actionBracket) import           Ide.PluginUtils                (installSigUsr1Handler) import           Foreign.Storable               (Storable (sizeOf)) import           HeapSize                       (recursiveSize, runHeapsize)-import           Language.Haskell.LSP.Types     (NormalizedFilePath,+import           Language.LSP.Types             (NormalizedFilePath,                                                  fromNormalizedFilePath) import           Numeric.Natural                (Natural) import           OpenTelemetry.Eventlog         (SpanInFlight, Synchronicity(Asynchronous), Instrument, addEvent, beginSpan, endSpan,@@ -42,20 +41,24 @@ import Data.Text.Encoding (encodeUtf8) import Ide.Types (PluginId (..)) import Development.IDE.Types.Location (Uri (..))+import Control.Monad.IO.Unlift  -- | Trace a handler using OpenTelemetry. Adds various useful info into tags in the OpenTelemetry span. otTracedHandler-    :: String -- ^ Message type+    :: MonadUnliftIO m+    => String -- ^ Message type     -> String -- ^ Message label-    -> (SpanInFlight -> IO a)-    -> IO a+    -> (SpanInFlight -> m a)+    -> m a otTracedHandler requestType label act =   let !name =         if null label           then requestType           else requestType <> ":" <> show label    -- Add an event so all requests can be quickly seen in the viewer without searching-   in withSpan (fromString name) (\sp -> addEvent sp "" (fromString $ name <> " received") >> act sp)+   in do+     runInIO <- askRunInIO+     liftIO $ withSpan (fromString name) (\sp -> addEvent sp "" (fromString $ name <> " received") >> runInIO (act sp))  otSetUri :: SpanInFlight -> Uri -> IO () otSetUri sp (Uri t) = setTag sp "uri" (encodeUtf8 t)@@ -81,14 +84,15 @@         return res)  #if MIN_GHC_API_VERSION(8,8,0)-otTracedProvider :: PluginId -> ByteString -> IO a -> IO a+otTracedProvider :: MonadUnliftIO m => PluginId -> ByteString -> m a -> m a #else-otTracedProvider :: PluginId -> String -> IO a -> IO a+otTracedProvider :: MonadUnliftIO m => PluginId -> String -> m a -> m a #endif-otTracedProvider (PluginId pluginName) provider act =-  withSpan (provider <> " provider") $ \sp -> do+otTracedProvider (PluginId pluginName) provider act = do+  runInIO <- askRunInIO+  liftIO $ withSpan (provider <> " provider") $ \sp -> do     setTag sp "plugin" (encodeUtf8 pluginName)-    act+    runInIO act  startTelemetry :: Bool -> Logger -> Var Values -> IO () startTelemetry allTheTime logger stateRef = do
src/Development/IDE/GHC/ExactPrint.hs view
@@ -1,19 +1,24 @@-{-# LANGUAGE CPP #-}+{-# LANGUAGE CPP                #-} {-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE RankNTypes #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE GADTs              #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE RankNTypes         #-}+{-# LANGUAGE TypeFamilies       #-} +{- HLINT ignore "Use zipFrom" -}+ module Development.IDE.GHC.ExactPrint     ( Graft(..),       graft,+      graftWithoutParentheses,       graftDecls,       graftDeclsWithM,       annotate,       hoistGraft,       graftWithM,       graftWithSmallestM,+      graftSmallestDecls,+      graftSmallestDeclsWithM,       transform,       transformM,       useAnnotatedSource,@@ -54,14 +59,22 @@ import Ide.PluginUtils import Language.Haskell.GHC.ExactPrint import Language.Haskell.GHC.ExactPrint.Parsers-import Language.Haskell.LSP.Types-import Language.Haskell.LSP.Types.Capabilities (ClientCapabilities)+import Language.LSP.Types+import Language.LSP.Types.Capabilities (ClientCapabilities) import Outputable (Outputable, ppr, showSDoc) import Retrie.ExactPrint hiding (parseDecl, parseExpr, parsePattern, parseType) import Parser (parseIdentifier)+import Data.Traversable (for)+import Data.Foldable (Foldable(fold))+import Data.Bool (bool) #if __GLASGOW_HASKELL__ == 808 import Control.Arrow #endif+#if __GLASGOW_HASKELL__ > 808+import Bag (listToBag)+import ErrUtils (mkErrMsg)+import Outputable (text, neverQualify)+#endif   ------------------------------------------------------------------------------@@ -179,8 +192,18 @@     SrcSpan ->     Located ast ->     Graft (Either String) a-graft dst val = Graft $ \dflags a -> do-    (anns, val') <- annotate dflags $ maybeParensAST val+graft dst = graftWithoutParentheses dst . maybeParensAST++-- | Like 'graft', but trusts that you have correctly inserted the parentheses+-- yourself. If you haven't, the resulting AST will not be valid!+graftWithoutParentheses ::+    forall ast a.+    (Data a, ASTElement ast) =>+    SrcSpan ->+    Located ast ->+    Graft (Either String) a+graftWithoutParentheses dst val = Graft $ \dflags a -> do+    (anns, val') <- annotate dflags val     modifyAnnsT $ mappend anns     pure $         everywhere'@@ -191,6 +214,7 @@             )             a + ------------------------------------------------------------------------------  graftWithM ::@@ -260,6 +284,44 @@             | otherwise = DL.singleton (L src e) <> go rest     modifyDeclsT (pure . DL.toList . go) a +graftSmallestDecls ::+    forall a.+    (HasDecls a) =>+    SrcSpan ->+    [LHsDecl GhcPs] ->+    Graft (Either String) a+graftSmallestDecls dst decs0 = Graft $ \dflags a -> do+    decs <- forM decs0 $ \decl -> do+        (anns, decl') <- annotateDecl dflags decl+        modifyAnnsT $ mappend anns+        pure decl'+    let go [] = DL.empty+        go (L src e : rest)+            | dst `isSubspanOf` src = DL.fromList decs <> DL.fromList rest+            | otherwise = DL.singleton (L src e) <> go rest+    modifyDeclsT (pure . DL.toList . go) a++graftSmallestDeclsWithM ::+    forall a.+    (HasDecls a) =>+    SrcSpan ->+    (LHsDecl GhcPs -> TransformT (Either String) (Maybe [LHsDecl GhcPs])) ->+    Graft (Either String) a+graftSmallestDeclsWithM dst toDecls = Graft $ \dflags a -> do+    let go [] = pure DL.empty+        go (e@(L src _) : rest)+            | dst `isSubspanOf` src = toDecls e >>= \case+                Just decs0 -> do+                    decs <- forM decs0 $ \decl -> do+                        (anns, decl') <-+                            annotateDecl dflags decl+                        modifyAnnsT $ mappend anns+                        pure decl'+                    pure $ DL.fromList decs <> DL.fromList rest+                Nothing -> (DL.singleton e <>) <$> go rest+            | otherwise = (DL.singleton e <>) <$> go rest+    modifyDeclsT (fmap DL.toList . go) a+ graftDeclsWithM ::     forall a m.     (HasDecls a, Fail.MonadFail m) =>@@ -344,12 +406,37 @@  -- | Given an 'LHsDecl', compute its exactprint annotations. annotateDecl :: DynFlags -> LHsDecl GhcPs -> TransformT (Either String) (Anns, LHsDecl GhcPs)+-- The 'parseDecl' function fails to parse 'FunBind' 'ValD's which contain+-- multiple matches. To work around this, we split the single+-- 'FunBind'-of-multiple-'Match'es into multiple 'FunBind's-of-one-'Match',+-- and then merge them all back together.+annotateDecl dflags+            (L src (+                ValD ext fb@FunBind+                  { fun_matches = mg@MG { mg_alts = L alt_src alts@(_:_)}+                  })) = do+    let set_matches matches =+          ValD ext fb { fun_matches = mg { mg_alts = L alt_src matches }}++    (anns', alts') <- fmap unzip $ for (zip [0..] alts) $ \(ix :: Int, alt) -> do+      uniq <- show <$> uniqueSrcSpanT+      let rendered = render dflags $ set_matches [alt]+      lift (mapLeft show $ parseDecl dflags uniq rendered) >>= \case+        (ann, L _ (ValD _ FunBind { fun_matches = MG { mg_alts = L _ [alt']}}))+           -> pure (bool id (setPrecedingLines alt' 1 0) (ix /= 0) ann, alt')+        _ ->  lift $ Left "annotateDecl: didn't parse a single FunBind match"++    let expr' = L src $ set_matches alts'+        anns'' = setPrecedingLines expr' 1 0 $ fold anns'++    pure (anns'', expr') annotateDecl dflags ast = do     uniq <- show <$> uniqueSrcSpanT     let rendered = render dflags ast     (anns, expr') <- lift $ mapLeft show $ parseDecl dflags uniq rendered     let anns' = setPrecedingLines expr' 1 0 anns     pure (anns', expr')+ ------------------------------------------------------------------------------  -- | Print out something 'Outputable'.
src/Development/IDE/GHC/Warnings.hs view
@@ -1,5 +1,6 @@ -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0+{-# LANGUAGE ExplicitNamespaces #-}  module Development.IDE.GHC.Warnings(withWarnings) where @@ -12,7 +13,7 @@  import           Development.IDE.Types.Diagnostics import           Development.IDE.GHC.Error-import           Language.Haskell.LSP.Types (NumberOrString (StringValue))+import           Language.LSP.Types (type (|?)(..))   -- | Take a GHC monadic action (e.g. @typecheckModule pm@ for some@@ -36,7 +37,7 @@   return (reverse $ concat warns, res)  attachReason :: WarnReason -> Diagnostic -> Diagnostic-attachReason wr d = d{_code = StringValue <$> showReason wr}+attachReason wr d = d{_code = InR <$> showReason wr}  where   showReason = \case     NoReason -> Nothing
src/Development/IDE/Import/DependencyInformation.hs view
@@ -46,8 +46,6 @@ import Data.IntSet (IntSet) import qualified Data.IntSet as IntSet import Data.Maybe-import Data.Set (Set)-import qualified Data.Set as Set import GHC.Generics (Generic)  import Development.IDE.Types.Diagnostics@@ -55,15 +53,12 @@ import Development.IDE.Import.FindImports (ArtifactsLocation(..))  import GHC-import Module  -- | The imports for a given module.-data ModuleImports = ModuleImports-    { moduleImports :: ![(Located ModuleName, Maybe FilePathId)]+newtype ModuleImports = ModuleImports+    { moduleImports :: [(Located ModuleName, Maybe FilePathId)]     -- ^ Imports of a module in the current package and the file path of     -- that module on disk (if we found it)-    , packageImports :: !(Set InstalledUnitId)-    -- ^ Transitive package dependencies unioned for all imports.     } deriving Show  -- | For processing dependency information, we need lots of maps and sets of@@ -132,10 +127,6 @@     , rawBootMap :: !BootIdMap     } deriving Show -pkgDependencies :: RawDependencyInformation -> FilePathIdMap (Set InstalledUnitId)-pkgDependencies RawDependencyInformation{..} =-    IntMap.map (either (const Set.empty) packageImports) rawImports- data DependencyInformation =   DependencyInformation     { depErrorNodes :: !(FilePathIdMap (NonEmpty NodeError))@@ -146,8 +137,6 @@     -- 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     -- ^ Map from FilePath to FilePathId     , depBootMap :: !BootIdMap@@ -222,13 +211,12 @@    SuccessNode a <> SuccessNode _ = SuccessNode a  processDependencyInformation :: RawDependencyInformation -> DependencyInformation-processDependencyInformation rawDepInfo@RawDependencyInformation{..} =+processDependencyInformation RawDependencyInformation{..} =   DependencyInformation     { depErrorNodes = IntMap.fromList errorNodes     , depModuleDeps = moduleDeps     , depReverseModuleDeps = reverseModuleDeps     , depModuleNames = IntMap.fromList $ coerce moduleNames-    , depPkgDeps = pkgDependencies rawDepInfo     , depPathIdMap = rawPathIdMap     , depBootMap = rawBootMap     }@@ -248,8 +236,8 @@             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+            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:@@ -345,17 +333,8 @@       reachable g <$> toVertex (getFilePathId fileId)   let transitiveModuleDepIds =         filter (\v -> v `IntSet.member` reachableVs) $ map (fst3 . fromVertex) vs-  let transitivePkgDeps =-          Set.toList $ Set.unions $-          map (\f -> IntMap.findWithDefault Set.empty f depPkgDeps) $-          getFilePathId fileId : transitiveModuleDepIds   let transitiveModuleDeps =         map (idToPath depPathIdMap . FilePathId) transitiveModuleDepIds-  let transitiveNamedModuleDeps =-        [ NamedModuleDep (idToPath depPathIdMap (FilePathId fid)) mn artifactModLocation-        | (fid, ShowableModuleName mn) <- IntMap.toList depModuleNames-        , let ArtifactsLocation{artifactModLocation} = idToPathMap depPathIdMap IntMap.! fid-        ]   pure TransitiveDependencies {..}   where     (g, fromVertex, toVertex) = graphFromEdges edges@@ -369,15 +348,10 @@      vs = topSort g -data TransitiveDependencies = TransitiveDependencies+newtype TransitiveDependencies = TransitiveDependencies   { transitiveModuleDeps :: [NormalizedFilePath]   -- ^ Transitive module dependencies in topological order.   -- The module itself is not included.-  , transitiveNamedModuleDeps :: [NamedModuleDep]-  -- ^ Transitive module dependencies in topological order.-  -- The module itself is not included.-  , transitivePkgDeps :: [InstalledUnitId]-  -- ^ Transitive pkg dependencies in unspecified order.   } deriving (Eq, Show, Generic)  instance NFData TransitiveDependencies
src/Development/IDE/Import/FindImports.hs view
@@ -37,7 +37,7 @@  data Import   = FileImport !ArtifactsLocation-  | PackageImport !M.InstalledUnitId+  | PackageImport   deriving (Show)  data ArtifactsLocation = ArtifactsLocation@@ -55,7 +55,7 @@  instance NFData Import where   rnf (FileImport x) = rnf x-  rnf (PackageImport x) = rnf x+  rnf PackageImport = ()  modSummaryToArtifactsLocation :: NormalizedFilePath -> Maybe ModSummary -> ArtifactsLocation modSummaryToArtifactsLocation nfp ms = ArtifactsLocation nfp (ms_location <$> ms) source@@ -137,7 +137,7 @@      lookupInPackageDB dfs =       case lookupModuleWithSuggestions dfs (unLoc modName) mbPkgName of-        LookupFound _m pkgConfig -> return $ Right $ PackageImport $ unitId pkgConfig+        LookupFound _m _pkgConfig -> return $ Right PackageImport         reason -> return $ Left $ notFoundErr dfs modName reason  -- | Don't call this on a found module.
src/Development/IDE/LSP/HoverDefinition.hs view
@@ -1,42 +1,39 @@ -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0-+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GADTs      #-}  -- | Display information on hover. module Development.IDE.LSP.HoverDefinition-    ( setHandlersDefinition-    , setHandlersTypeDefinition-    , setHandlersDocHighlight-    , setHandlersReferences-    , setHandlersWsSymbols+    ( setIdeHandlers     -- * For haskell-language-server     , hover     , gotoDefinition     , gotoTypeDefinition     ) where +import Control.Monad.IO.Class import           Development.IDE.Core.Rules import           Development.IDE.Core.Shake import           Development.IDE.LSP.Server import           Development.IDE.Types.Location import           Development.IDE.Types.Logger-import qualified Language.Haskell.LSP.Core       as LSP-import           Language.Haskell.LSP.Messages-import           Language.Haskell.LSP.Types+import qualified Language.LSP.Server as LSP+import           Language.LSP.Types  import qualified Data.Text as T -gotoDefinition :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError LocationResponseParams)-hover          :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError (Maybe Hover))-gotoTypeDefinition :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError LocationResponseParams)-documentHighlight :: IdeState -> TextDocumentPositionParams -> IO (Either ResponseError (List DocumentHighlight))-gotoDefinition = request "Definition" getDefinition (MultiLoc []) MultiLoc-gotoTypeDefinition = request "TypeDefinition" getTypeDefinition (MultiLoc []) MultiLoc+gotoDefinition :: IdeState -> TextDocumentPositionParams -> LSP.LspM c (Either ResponseError (ResponseResult TextDocumentDefinition))+hover          :: IdeState -> TextDocumentPositionParams -> LSP.LspM c (Either ResponseError (Maybe Hover))+gotoTypeDefinition :: IdeState -> TextDocumentPositionParams -> LSP.LspM c (Either ResponseError (ResponseResult TextDocumentTypeDefinition))+documentHighlight :: IdeState -> TextDocumentPositionParams -> LSP.LspM c (Either ResponseError (List DocumentHighlight))+gotoDefinition = request "Definition" getDefinition (InR $ InL $ List []) (InR . InL . List)+gotoTypeDefinition = request "TypeDefinition" getTypeDefinition (InR $ InL $ List []) (InR . InL . List) hover          = request "Hover"      getAtPoint     Nothing      foundHover documentHighlight = request "DocumentHighlight" highlightAtPoint (List []) List -references :: IdeState -> ReferenceParams -> IO (Either ResponseError (List Location))-references ide (ReferenceParams (TextDocumentIdentifier uri) pos _ _) =+references :: IdeState -> ReferenceParams -> LSP.LspM c (Either ResponseError (List Location))+references ide (ReferenceParams (TextDocumentIdentifier uri) pos _ _ _) = liftIO $   case uriToFilePath' uri of     Just path -> do       let filePath = toNormalizedFilePath' path@@ -46,8 +43,8 @@       Right . List <$> (runAction "references" ide $ refsAtPoint filePath pos)     Nothing -> pure $ Left $ ResponseError InvalidParams ("Invalid URI " <> T.pack (show uri)) Nothing -wsSymbols :: IdeState -> WorkspaceSymbolParams -> IO (Either ResponseError (List SymbolInformation))-wsSymbols ide (WorkspaceSymbolParams query _) = do+wsSymbols :: IdeState -> WorkspaceSymbolParams -> LSP.LspM c (Either ResponseError (List SymbolInformation))+wsSymbols ide (WorkspaceSymbolParams _ _ query) = liftIO $ do   logDebug (ideLogger ide) $ "Workspace symbols request: " <> query   runIdeAction "WorkspaceSymbols" (shakeExtras ide) $ Right . maybe (List []) List <$> workspaceSymbols query @@ -55,18 +52,17 @@ foundHover (mbRange, contents) =   Just $ Hover (HoverContents $ MarkupContent MkMarkdown $ T.intercalate sectionSeparator contents) mbRange -setHandlersDefinition, setHandlersTypeDefinition, setHandlersDocHighlight,-  setHandlersReferences, setHandlersWsSymbols :: PartialHandlers c-setHandlersDefinition = PartialHandlers $ \WithMessage{..} x ->-  return x{LSP.definitionHandler = withResponse RspDefinition $ const gotoDefinition}-setHandlersTypeDefinition = PartialHandlers $ \WithMessage{..} x ->-  return x {LSP.typeDefinitionHandler = withResponse RspDefinition $ const gotoTypeDefinition}-setHandlersDocHighlight = PartialHandlers $ \WithMessage{..} x ->-  return x{LSP.documentHighlightHandler = withResponse RspDocumentHighlights $ const documentHighlight}-setHandlersReferences = PartialHandlers $ \WithMessage{..} x ->-  return x{LSP.referencesHandler = withResponse RspFindReferences $ const references}-setHandlersWsSymbols = PartialHandlers $ \WithMessage{..} x ->-  return x{LSP.workspaceSymbolHandler = withResponse RspWorkspaceSymbols $ const wsSymbols}+setIdeHandlers :: LSP.Handlers (ServerM c)+setIdeHandlers = mconcat+  [ requestHandler STextDocumentDefinition $ \ide DefinitionParams{..} ->+      gotoDefinition ide TextDocumentPositionParams{..}+  , requestHandler STextDocumentTypeDefinition $ \ide TypeDefinitionParams{..} ->+      gotoTypeDefinition ide TextDocumentPositionParams{..}+  , requestHandler STextDocumentDocumentHighlight $ \ide DocumentHighlightParams{..} ->+      documentHighlight ide TextDocumentPositionParams{..}+  , requestHandler STextDocumentReferences references+  , requestHandler SWorkspaceSymbol wsSymbols+  ]  -- | Respond to and log a hover or go-to-definition request request@@ -76,8 +72,8 @@   -> (a -> b)   -> IdeState   -> TextDocumentPositionParams-  -> IO (Either ResponseError b)-request label getResults notFound found ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos _) = do+  -> LSP.LspM c (Either ResponseError b)+request label getResults notFound found ide (TextDocumentPositionParams (TextDocumentIdentifier uri) pos) = liftIO $ do     mbResult <- case uriToFilePath' uri of         Just path -> logAndRunRequest label getResults ide pos path         Nothing   -> pure Nothing@@ -86,7 +82,7 @@ logAndRunRequest :: T.Text -> (NormalizedFilePath -> Position -> IdeAction b) -> IdeState -> Position -> String -> IO b logAndRunRequest label getResults ide pos path = do   let filePath = toNormalizedFilePath' path-  logInfo (ideLogger ide) $+  logDebug (ideLogger ide) $     label <> " request at position " <> T.pack (showPosition pos) <>     " in file: " <> T.pack path   runIdeAction (T.unpack label) (shakeExtras ide) (getResults filePath pos)
src/Development/IDE/LSP/LanguageServer.hs view
@@ -1,8 +1,10 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.+      -- Copyright (c) 2019 The DAML Authors. All rights reserved. -- SPDX-License-Identifier: Apache-2.0  {-# LANGUAGE ExistentialQuantification  #-}-{-# LANGUAGE RankNTypes                 #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE RankNTypes #-}  -- WARNING: A copy of DA.Daml.LanguageServer, try to keep them in sync -- This version removes the daml: handling@@ -10,46 +12,47 @@     ( runLanguageServer     ) where -import           Language.Haskell.LSP.Types-import           Language.Haskell.LSP.Types.Capabilities+import           Language.LSP.Types import           Development.IDE.LSP.Server import qualified Development.IDE.GHC.Util as Ghcide-import qualified Language.Haskell.LSP.Control as LSP-import qualified Language.Haskell.LSP.Core as LSP-import Control.Concurrent.Chan-import Control.Concurrent.Extra-import Control.Concurrent.Async+import qualified Language.LSP.Server as LSP+import Control.Concurrent.Extra (newBarrier, signalBarrier, waitBarrier) import Control.Concurrent.STM-import Control.Exception.Safe-import Data.Default import Data.Maybe+import Data.Aeson (Value) import qualified Data.Set as Set import qualified Data.Text as T import GHC.IO.Handle (hDuplicate) import System.IO import Control.Monad.Extra+import UnliftIO.Exception+import UnliftIO.Async+import UnliftIO.Concurrent+import UnliftIO.Directory+import Control.Monad.IO.Class+import Control.Monad.Reader+import Ide.Types (traceWithSpan)+import Development.IDE.Session (runWithDb)  import Development.IDE.Core.IdeConfiguration import Development.IDE.Core.Shake import Development.IDE.LSP.HoverDefinition import Development.IDE.LSP.Notifications-import Development.IDE.LSP.Outline import Development.IDE.Types.Logger import Development.IDE.Core.FileStore import Development.IDE.Core.Tracing-import Language.Haskell.LSP.Core (LspFuncs(..))-import Language.Haskell.LSP.Messages +import System.IO.Unsafe (unsafeInterleaveIO)+ runLanguageServer     :: forall config. (Show config)     => LSP.Options-    -> PartialHandlers config-    -> (InitializeRequest -> Either T.Text config)-    -> (DidChangeConfigurationNotification -> Either T.Text config)-    -> (IO LspId -> (FromServerMessage -> IO ()) -> VFSHandle -> ClientCapabilities-        -> WithProgressFunc -> WithIndefiniteProgressFunc -> IO (Maybe config) -> Maybe FilePath -> IO IdeState)+    -> (FilePath -> IO FilePath) -- ^ Map root paths to the location of the hiedb for the project+    -> (IdeState -> Value -> IO (Either T.Text config))+    -> LSP.Handlers (ServerM config)+    -> (LSP.LanguageContextEnv config -> VFSHandle -> Maybe FilePath -> HieDb -> IndexQueue -> IO IdeState)     -> IO ()-runLanguageServer options userHandlers onInitialConfig onConfigChange getIdeState = do+runLanguageServer options getHieDbLoc onConfigurationChange userHandlers getIdeState = do     -- Move stdout to another file descriptor and duplicate stderr     -- to stdout. This guards against stray prints from corrupting the JSON-RPC     -- message stream.@@ -64,10 +67,6 @@     -- the language server tests without the redirection.     putStr " " >> hFlush stdout -    -- Send everything over a channel, since you need to wait until after initialise before-    -- LspFuncs is available-    clientMsgChan :: Chan (Message config) <- newChan-     -- These barriers are signaled when the threads reading from these chans exit.     -- 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.@@ -80,16 +79,6 @@     -- The set of requests that have been cancelled and are also in pendingRequests     cancelledRequests <- newTVarIO Set.empty -    let withResponse wrap f = Just $ \r@RequestMessage{_id, _method} -> do-            atomically $ modifyTVar pendingRequests (Set.insert _id)-            writeChan clientMsgChan $ Response r wrap f-    let withNotification old f = Just $ \r@NotificationMessage{_method} ->-            writeChan clientMsgChan $ Notification r (\lsp ide x -> f lsp ide x >> whenJust old ($ r))-    let withResponseAndRequest wrap wrapNewReq f = Just $ \r@RequestMessage{_id, _method} -> do-            atomically $ modifyTVar pendingRequests (Set.insert _id)-            writeChan clientMsgChan $ ResponseAndRequest r wrap wrapNewReq f-    let withInitialize f = Just $ \r ->-            writeChan clientMsgChan $ InitialParams r (\lsp ide x -> f lsp ide x)     let cancelRequest reqId = atomically $ do             queued <- readTVar pendingRequests             -- We want to avoid that the list of cancelled requests@@ -105,160 +94,117 @@     let waitForCancel reqId = atomically $ do             cancelled <- readTVar cancelledRequests             unless (reqId `Set.member` cancelled) retry-    let PartialHandlers parts =-            initializeRequestHandler <>-            setHandlersIgnore <> -- least important-            setHandlersDefinition <> setHandlersTypeDefinition <>-            setHandlersDocHighlight <> setHandlersReferences <> setHandlersWsSymbols <>-            setHandlersOutline <>-            userHandlers <>-            setHandlersNotifications <> -- absolutely critical, join them with user notifications-            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 -    let initializeCallbacks = LSP.InitializeCallbacks-            { LSP.onInitialConfiguration = onInitialConfig-            , LSP.onConfigurationChange = onConfigChange-            , LSP.onStartup = handleInit exit clearReqId waitForCancel clientMsgChan+    let ideHandlers = mconcat+          [ setIdeHandlers+          , userHandlers+          , setHandlersNotifications -- absolutely critical, join them with user notifications+          ]++    -- Send everything over a channel, since you need to wait until after initialise before+    -- LspFuncs is available+    clientMsgChan :: Chan ReactorMessage <- newChan++    let asyncHandlers = mconcat+          [ ideHandlers+          , 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.+++    let serverDefinition = LSP.ServerDefinition+            { LSP.onConfigurationChange = \v -> do+                (_chan, ide) <- ask+                liftIO $ onConfigurationChange ide v+            , LSP.doInitialize = handleInit exit clearReqId waitForCancel clientMsgChan+            , LSP.staticHandlers = asyncHandlers+            , LSP.interpretHandler = \(env, st) -> LSP.Iso (LSP.runLspT env . flip runReaderT (clientMsgChan,st)) liftIO+            , LSP.options = modifyOptions options             }      void $ waitAnyCancel =<< traverse async-        [ void $ LSP.runWithHandles+        [ void $ LSP.runServerWithHandles             stdin             newStdout-            initializeCallbacks-            handlers-            (modifyOptions options)-            Nothing+            serverDefinition         , void $ waitBarrier clientMsgBarrier         ]+     where-        handleInit :: IO () -> (LspId -> IO ()) -> (LspId -> IO ()) -> Chan (Message config) -> LSP.LspFuncs config -> IO (Maybe err)-        handleInit exitClientMsg clearReqId waitForCancel clientMsgChan lspFuncs@LSP.LspFuncs{..} = do+        handleInit+          :: IO () -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> Chan ReactorMessage+          -> LSP.LanguageContextEnv config -> RequestMessage Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState))+        handleInit exitClientMsg clearReqId waitForCancel clientMsgChan env (RequestMessage _ _ m params) = otTracedHandler "Initialize" (show m) $ \sp -> do+            traceWithSpan sp params+            let root = LSP.resRootPath env -            ide <- getIdeState getNextReqId sendFunc (makeLSPVFSHandle lspFuncs) clientCapabilities-                               withProgress withIndefiniteProgress config rootPath+            dir <- getCurrentDirectory+            dbLoc <- getHieDbLoc dir -            _ <- flip forkFinally (const exitClientMsg) $ forever $ do+            -- The database needs to be open for the duration of the reactor thread, but we need to pass in a reference+            -- to 'getIdeState', so we use this dirty trick+            dbMVar <- newEmptyMVar+            ~(hiedb,hieChan) <- unsafeInterleaveIO $ takeMVar dbMVar++            ide <- getIdeState env (makeLSPVFSHandle env) root hiedb hieChan++            let initConfig = parseConfiguration params+            logInfo (ideLogger ide) $ T.pack $ "Registering ide configuration: " <> show initConfig+            registerIdeConfiguration (shakeExtras ide) initConfig++            _ <- flip forkFinally (const exitClientMsg) $ runWithDb dbLoc $ \hiedb hieChan -> do+              putMVar dbMVar (hiedb,hieChan)+              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, _method} act ->-                        otTracedHandler "Notification" (show _method) $ \sp -> do-                          traceWithSpan sp _params-                          catch (act lspFuncs ide _params) $ \(e :: SomeException) ->-                            logError (ideLogger ide) $ T.pack $-                                "Unexpected exception on notification, please report!\n" ++-                                "Message: " ++ show x ++ "\n" ++-                                "Exception: " ++ show e-                    Response x@RequestMessage{_id, _method, _params} wrap act -> void $ async $-                        otTracedHandler "Request" (show _method) $ \sp -> do-                          traceWithSpan sp _params-                          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, _method, _params} wrap wrapNewReq act -> void $ async $-                        otTracedHandler "Request" (show _method) $ \sp -> do-                          traceWithSpan sp _params-                          checkCancelled ide clearReqId waitForCancel lspFuncs wrap act x _id _params $-                            \(res, newReq) -> do-                                case res of-                                    Left e  -> sendFunc $ wrap $ ResponseMessage "2.0" (responseId _id) (Left e)-                                    Right r -> sendFunc $ wrap $ ResponseMessage "2.0" (responseId _id) (Right r)-                                whenJust newReq $ \(rm, newReqParams) -> do-                                    reqId <- getNextReqId-                                    sendFunc $ wrapNewReq $ RequestMessage "2.0" reqId rm newReqParams-                    InitialParams x@RequestMessage{_id, _method, _params} act ->-                        otTracedHandler "Initialize" (show _method) $ \sp -> do-                          traceWithSpan sp _params-                          catch (act lspFuncs ide _params) $ \(e :: SomeException) ->-                            logError (ideLogger ide) $ T.pack $-                                "Unexpected exception on InitializeRequest handler, please report!\n" ++-                                "Message: " ++ show x ++ "\n" ++-                                "Exception: " ++ show e-            pure Nothing+                    ReactorNotification act -> do+                      catch act $ \(e :: SomeException) ->+                        logError (ideLogger ide) $ T.pack $+                          "Unexpected exception on notification, please report!\n" +++                          "Exception: " ++ show e+                    ReactorRequest _id act k -> void $ async $+                      checkCancelled ide clearReqId waitForCancel _id act k+            pure $ Right (env,ide) -        checkCancelled ide clearReqId waitForCancel lspFuncs@LSP.LspFuncs{..} wrap act msg _id _params k =+        checkCancelled+          :: IdeState -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> SomeLspId+          -> IO () -> (ResponseError -> IO ()) -> IO ()+        checkCancelled ide clearReqId waitForCancel _id act k =             flip finally (clearReqId _id) $                 catch (do                     -- We could optimize this by first checking if the id                     -- is in the cancelled set. However, this is unlikely to be a                     -- bottleneck and the additional check might hide                     -- issues with async exceptions that need to be fixed.-                    cancelOrRes <- race (waitForCancel _id) $ act lspFuncs ide _params+                    cancelOrRes <- race (waitForCancel _id) act                     case cancelOrRes of                         Left () -> do-                            logDebug (ideLogger ide) $ T.pack $-                                "Cancelled request " <> show _id-                            sendFunc $ wrap $ ResponseMessage "2.0" (responseId _id) $ Left-                                $ ResponseError RequestCancelled "" Nothing-                        Right res -> k res+                            logDebug (ideLogger ide) $ T.pack $ "Cancelled request " <> show _id+                            k $ ResponseError RequestCancelled "" Nothing+                        Right res -> pure res                 ) $ \(e :: SomeException) -> do                     logError (ideLogger ide) $ T.pack $                         "Unexpected exception on request, please report!\n" ++-                        "Message: " ++ show msg ++ "\n" ++                         "Exception: " ++ show e-                    sendFunc $ wrap $ ResponseMessage "2.0" (responseId _id) $ Left-                        $ ResponseError InternalError (T.pack $ show e) Nothing+                    k $ ResponseError InternalError (T.pack $ show e) Nothing -initializeRequestHandler :: PartialHandlers config-initializeRequestHandler = PartialHandlers $ \WithMessage{..} x -> return x{-    LSP.initializeRequestHandler = withInitialize initHandler-    } -initHandler-    :: LSP.LspFuncs c-    -> IdeState-    -> InitializeParams-    -> IO ()-initHandler _ ide params = do-    let initConfig = parseConfiguration params-    logInfo (ideLogger ide) $ T.pack $ "Registering ide configuration: " <> show initConfig-    registerIdeConfiguration (shakeExtras ide) initConfig---- | Things that get sent to us, but we don't deal with.---   Set them to avoid a warning in VS Code output.-setHandlersIgnore :: PartialHandlers config-setHandlersIgnore = PartialHandlers $ \_ x -> return x-    {LSP.responseHandler = none-    }-    where none = Just $ const $ return ()--cancelHandler :: (LspId -> IO ()) -> PartialHandlers config-cancelHandler cancelRequest = PartialHandlers $ \_ x -> return x-    {LSP.cancelNotificationHandler = Just $ \msg@NotificationMessage {_params = CancelParams {_id}} -> do-            cancelRequest _id-            whenJust (LSP.cancelNotificationHandler x) ($ msg)-    }--exitHandler :: IO () -> PartialHandlers c-exitHandler exit = PartialHandlers $ \_ x -> return x-    {LSP.exitNotificationHandler = Just $ const exit}+cancelHandler :: (SomeLspId -> IO ()) -> LSP.Handlers (ServerM c)+cancelHandler cancelRequest = LSP.notificationHandler SCancelRequest $ \NotificationMessage{_params=CancelParams{_id}} ->+  liftIO $ cancelRequest (SomeLspId _id) --- | 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)-data Message c-  = forall m req resp . (Show m, Show req, HasTracing req) =>-    Response (RequestMessage m req resp) (ResponseMessage resp -> FromServerMessage) (LSP.LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp))-  | -- | Used for cases in which we need to send not only a response,-    --   but also an additional request to the client.-    --   For example, 'executeCommand' may generate an 'applyWorkspaceEdit' request.-    forall m rm req resp newReqParams newReqBody. (Show m, Show rm, Show req, HasTracing req) =>-    ResponseAndRequest (RequestMessage m req resp) (ResponseMessage resp -> FromServerMessage) (RequestMessage rm newReqParams newReqBody -> FromServerMessage) (LSP.LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp, Maybe (rm, newReqParams)))-  | forall m req . (Show m, Show req, HasTracing req) =>-    Notification (NotificationMessage m req) (LSP.LspFuncs c -> IdeState -> req -> IO ())-  | -- | Used for the InitializeRequest only, where the response is generated by the LSP core handler.-    InitialParams InitializeRequest (LSP.LspFuncs c -> IdeState -> InitializeParams -> IO ())+exitHandler :: IO () -> LSP.Handlers (ServerM c)+exitHandler exit = LSP.notificationHandler SExit (const $ liftIO exit)  modifyOptions :: LSP.Options -> LSP.Options modifyOptions x = x{ LSP.textDocumentSync   = Just $ tweakTDS origTDS                    }     where-        tweakTDS tds = tds{_openClose=Just True, _change=Just TdSyncIncremental, _save=Just $ SaveOptions Nothing}+        tweakTDS tds = tds{_openClose=Just True, _change=Just TdSyncIncremental, _save=Just $ InR $ SaveOptions Nothing}         origTDS = fromMaybe tdsDefault $ LSP.textDocumentSync x         tdsDefault = TextDocumentSyncOptions Nothing Nothing Nothing Nothing Nothing+
src/Development/IDE/LSP/Notifications.hs view
@@ -3,27 +3,27 @@  {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE RankNTypes            #-}+{-# LANGUAGE GADTs                 #-}+{-# LANGUAGE PolyKinds             #-}  module Development.IDE.LSP.Notifications     ( setHandlersNotifications     ) where -import           Development.IDE.LSP.Server-import qualified Language.Haskell.LSP.Core        as LSP-import           Language.Haskell.LSP.Types-import qualified Language.Haskell.LSP.Types       as LSP-import qualified Language.Haskell.LSP.Messages    as LSP-import qualified Language.Haskell.LSP.Types.Capabilities as LSP+import qualified Language.LSP.Server      as LSP+import           Language.LSP.Types+import qualified Language.LSP.Types       as LSP+import qualified Language.LSP.Types.Capabilities as LSP  import           Development.IDE.Core.IdeConfiguration import           Development.IDE.Core.Service+import           Development.IDE.LSP.Server 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 qualified Data.Aeson                       as A import           Data.Foldable                    as F import           Data.Maybe import qualified Data.HashMap.Strict              as M@@ -34,115 +34,111 @@ import           Development.IDE.Core.FileExists  (modifyFileExists, watchedGlobs) import           Development.IDE.Core.OfInterest import Ide.Plugin.Config (CheckParents(CheckOnClose))+import Control.Monad.IO.Class   whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO () whenUriFile uri act = whenJust (LSP.uriToFilePath uri) $ act . toNormalizedFilePath' -setHandlersNotifications :: PartialHandlers c-setHandlersNotifications = PartialHandlers $ \WithMessage{..} x -> return x-    {LSP.didOpenTextDocumentNotificationHandler = withNotification (LSP.didOpenTextDocumentNotificationHandler x) $-        \_ ide (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> do-            updatePositionMapping ide (VersionedTextDocumentIdentifier _uri (Just _version)) (List [])-            whenUriFile _uri $ \file -> do-                -- 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 -> do-              modifyFilesOfInterest ide (M.insert file Modified)-              setFileModified ide False file-            logInfo (ideLogger ide) $ "Modified text document: " <> getUri _uri+setHandlersNotifications :: LSP.Handlers (ServerM c)+setHandlersNotifications = mconcat+  [ notificationHandler LSP.STextDocumentDidOpen $+      \ide (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> liftIO $ do+      updatePositionMapping ide (VersionedTextDocumentIdentifier _uri (Just _version)) (List [])+      whenUriFile _uri $ \file -> do+          -- 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{firstOpen=True})+          setFileModified ide False file+          logDebug (ideLogger ide) $ "Opened text document: " <> getUri _uri -    ,LSP.didSaveTextDocumentNotificationHandler = withNotification (LSP.didSaveTextDocumentNotificationHandler x) $-        \_ ide (DidSaveTextDocumentParams TextDocumentIdentifier{_uri}) -> do-            whenUriFile _uri $ \file -> do-                modifyFilesOfInterest ide (M.insert file OnDisk)-                setFileModified ide True file-            logInfo (ideLogger ide) $ "Saved text document: " <> getUri _uri+  , notificationHandler LSP.STextDocumentDidChange $+      \ide (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> liftIO $ do+        updatePositionMapping ide identifier changes+        whenUriFile _uri $ \file -> do+          modifyFilesOfInterest ide (M.insert file Modified{firstOpen=False})+          setFileModified ide False file+        logDebug (ideLogger ide) $ "Modified text document: " <> getUri _uri -    ,LSP.didCloseTextDocumentNotificationHandler = withNotification (LSP.didCloseTextDocumentNotificationHandler x) $-        \_ ide (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> do-            whenUriFile _uri $ \file -> do-                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-            -- See Note [File existence cache and LSP file watchers] which explains why we get these notifications and-            -- what we do with them-            let events =-                    mapMaybe-                        (\(FileEvent uri ev) ->-                            (, ev /= FcDeleted) . toNormalizedFilePath'-                            <$> LSP.uriToFilePath uri-                        )-                        ( F.toList fileEvents )-            let msg = Text.pack $ show events-            logDebug (ideLogger ide) $ "Files created or deleted: " <> msg-            modifyFileExists ide events-            setSomethingModified ide+  , notificationHandler LSP.STextDocumentDidSave $+      \ide (DidSaveTextDocumentParams TextDocumentIdentifier{_uri} _) -> liftIO $ do+        whenUriFile _uri $ \file -> do+            modifyFilesOfInterest ide (M.insert file OnDisk)+            setFileModified ide True file+        logDebug (ideLogger ide) $ "Saved text document: " <> getUri _uri -    ,LSP.didChangeWorkspaceFoldersNotificationHandler = withNotification (LSP.didChangeWorkspaceFoldersNotificationHandler x) $-        \_ ide (DidChangeWorkspaceFoldersParams events) -> do-            let add       = S.union-                substract = flip S.difference-            modifyWorkspaceFolders ide-              $ add       (foldMap (S.singleton . parseWorkspaceFolder) (_added   events))-              . substract (foldMap (S.singleton . parseWorkspaceFolder) (_removed events))+  , notificationHandler LSP.STextDocumentDidClose $+        \ide (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> liftIO $ do+          whenUriFile _uri $ \file -> do+              modifyFilesOfInterest ide (M.delete file)+              -- Refresh all the files that depended on this+              checkParents <- optCheckParents =<< getIdeOptionsIO (shakeExtras ide)+              when (checkParents >= CheckOnClose) $ typecheckParents ide file+              logDebug (ideLogger ide) $ "Closed text document: " <> getUri _uri -    ,LSP.didChangeConfigurationParamsHandler = withNotification (LSP.didChangeConfigurationParamsHandler x) $-        \_ ide (DidChangeConfigurationParams cfg) -> do-            let msg = Text.pack $ show cfg-            logInfo (ideLogger ide) $ "Configuration changed: " <> msg-            modifyClientSettings ide (const $ Just cfg)-            setSomethingModified ide+  , notificationHandler LSP.SWorkspaceDidChangeWatchedFiles $+      \ide (DidChangeWatchedFilesParams fileEvents) -> liftIO $ do+        -- See Note [File existence cache and LSP file watchers] which explains why we get these notifications and+        -- what we do with them+        let events =+                mapMaybe+                    (\(FileEvent uri ev) ->+                        (, ev /= FcDeleted) . toNormalizedFilePath'+                        <$> LSP.uriToFilePath uri+                    )+                    ( F.toList fileEvents )+        let msg = Text.pack $ show events+        logDebug (ideLogger ide) $ "Files created or deleted: " <> msg+        modifyFileExists ide events+        setSomethingModified ide -    -- Initialized handler, good time to dynamically register capabilities-    ,LSP.initializedHandler = withNotification (LSP.initializedHandler x) $ \lsp@LSP.LspFuncs{..} ide _ -> do-        let watchSupported = case () of-              _ | LSP.ClientCapabilities{_workspace} <- clientCapabilities-                , Just LSP.WorkspaceClientCapabilities{_didChangeWatchedFiles} <- _workspace-                , Just LSP.DidChangeWatchedFilesClientCapabilities{_dynamicRegistration} <- _didChangeWatchedFiles-                , Just True <- _dynamicRegistration-                  -> True-                | otherwise -> False+  , notificationHandler LSP.SWorkspaceDidChangeWorkspaceFolders $+      \ide (DidChangeWorkspaceFoldersParams events) -> liftIO $ do+        let add       = S.union+            substract = flip S.difference+        modifyWorkspaceFolders ide+          $ add       (foldMap (S.singleton . parseWorkspaceFolder) (_added   events))+          . substract (foldMap (S.singleton . parseWorkspaceFolder) (_removed events)) -        if watchSupported-        then registerWatcher lsp ide-        else logDebug (ideLogger ide) "Warning: Client does not support watched files. Falling back to OS polling"+  , notificationHandler LSP.SWorkspaceDidChangeConfiguration $+      \ide (DidChangeConfigurationParams cfg) -> liftIO $ do+        let msg = Text.pack $ show cfg+        logDebug (ideLogger ide) $ "Configuration changed: " <> msg+        modifyClientSettings ide (const $ Just cfg)+        setSomethingModified ide -    }-    where-        registerWatcher LSP.LspFuncs{..} ide = do-            lspId <- getNextReqId-            opts <- getIdeOptionsIO $ shakeExtras ide-            let-              req = RequestMessage "2.0" lspId ClientRegisterCapability regParams-              regParams    = RegistrationParams (List [registration])-              -- The registration ID is arbitrary and is only used in case we want to deregister (which we won't).-              -- We could also use something like a random UUID, as some other servers do, but this works for-              -- our purposes.-              registration = Registration "globalFileWatches"-                                          WorkspaceDidChangeWatchedFiles-                                          (Just (A.toJSON regOptions))-              regOptions =-                DidChangeWatchedFilesRegistrationOptions { _watchers = List watchers }-              -- See Note [File existence cache and LSP file watchers] for why this exists, and the choice of watch kind-              watchKind = WatchKind { _watchCreate = True, _watchChange = False, _watchDelete = True}-              -- See Note [Which files should we watch?] for an explanation of why the pattern is the way that it is-              -- The patterns will be something like "**/.hs", i.e. "any number of directory segments,-              -- followed by a file with an extension 'hs'.-              watcher glob = FileSystemWatcher { _globPattern = glob, _kind = Just watchKind }-              -- We use multiple watchers instead of one using '{}' because lsp-test doesn't-              -- support that: https://github.com/bubba/lsp-test/issues/77-              watchers = [ watcher glob | glob <- watchedGlobs opts ]+  , notificationHandler LSP.SInitialized $ \ide _ -> do+      clientCapabilities <- LSP.getClientCapabilities+      let watchSupported = case () of+            _ | LSP.ClientCapabilities{_workspace} <- clientCapabilities+              , Just LSP.WorkspaceClientCapabilities{_didChangeWatchedFiles} <- _workspace+              , Just LSP.DidChangeWatchedFilesClientCapabilities{_dynamicRegistration} <- _didChangeWatchedFiles+              , Just True <- _dynamicRegistration+                -> True+              | otherwise -> False+      if watchSupported+      then do+        opts <- liftIO $ getIdeOptionsIO $ shakeExtras ide+        let+          regParams    = RegistrationParams (List [SomeRegistration registration])+          -- The registration ID is arbitrary and is only used in case we want to deregister (which we won't).+          -- We could also use something like a random UUID, as some other servers do, but this works for+          -- our purposes.+          registration = Registration "globalFileWatches"+                                      SWorkspaceDidChangeWatchedFiles+                                      regOptions+          regOptions =+            DidChangeWatchedFilesRegistrationOptions { _watchers = List watchers }+          -- See Note [File existence cache and LSP file watchers] for why this exists, and the choice of watch kind+          watchKind = WatchKind { _watchCreate = True, _watchChange = False, _watchDelete = True}+          -- See Note [Which files should we watch?] for an explanation of why the pattern is the way that it is+          -- The patterns will be something like "**/.hs", i.e. "any number of directory segments,+          -- followed by a file with an extension 'hs'.+          watcher glob = FileSystemWatcher { _globPattern = glob, _kind = Just watchKind }+          -- We use multiple watchers instead of one using '{}' because lsp-test doesn't+          -- support that: https://github.com/bubba/lsp-test/issues/77+          watchers = [ watcher (Text.pack glob) | glob <- watchedGlobs opts ] -            sendFunc $ LSP.ReqRegisterCapability req+        void $ LSP.sendRequest SClientRegisterCapability regParams (const $ pure ()) -- TODO handle response+      else liftIO $ logDebug (ideLogger ide) "Warning: Client does not support watched files. Falling back to OS polling"+  ]
src/Development/IDE/LSP/Outline.hs view
@@ -1,17 +1,18 @@ {-# LANGUAGE CPP #-}++{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE DuplicateRecordFields #-} #include "ghc-api-version.h"  module Development.IDE.LSP.Outline-  ( setHandlersOutline-    -- * For haskell-language-server-  , moduleOutline+  ( moduleOutline   ) where -import qualified Language.Haskell.LSP.Core     as LSP-import           Language.Haskell.LSP.Messages-import           Language.Haskell.LSP.Types+import           Language.LSP.Types+import           Language.LSP.Server (LspM)+import           Control.Monad.IO.Class import           Data.Functor import           Data.Generics import           Data.Maybe@@ -23,26 +24,20 @@ import           Development.IDE.Core.Shake import           Development.IDE.GHC.Compat import           Development.IDE.GHC.Error      ( realSrcSpanToRange )-import           Development.IDE.LSP.Server import           Development.IDE.Types.Location import           Outputable                     ( Outputable                                                 , ppr                                                 , showSDocUnsafe                                                 ) -setHandlersOutline :: PartialHandlers c-setHandlersOutline = PartialHandlers $ \WithMessage {..} x -> return x-  { LSP.documentSymbolHandler = withResponse RspDocumentSymbols moduleOutline-  }- moduleOutline-  :: LSP.LspFuncs c -> IdeState -> DocumentSymbolParams -> IO (Either ResponseError DSResult)-moduleOutline _lsp ideState DocumentSymbolParams { _textDocument = TextDocumentIdentifier uri }-  = case uriToFilePath uri of+  :: IdeState -> DocumentSymbolParams -> LspM c (Either ResponseError (List DocumentSymbol |? List SymbolInformation))+moduleOutline ideState DocumentSymbolParams{ _textDocument = TextDocumentIdentifier uri }+  = liftIO $ case uriToFilePath uri of     Just (toNormalizedFilePath' -> fp) -> do       mb_decls <- fmap fst <$> runAction "Outline" ideState (useWithStale GetParsedModule fp)       pure $ Right $ case mb_decls of-        Nothing -> DSDocumentSymbols (List [])+        Nothing -> InL (List [])         Just ParsedModule { pm_parsed_source = L _ltop HsModule { hsmodName, hsmodDecls, hsmodImports } }           -> let                declSymbols  = mapMaybe documentSymbolForDecl hsmodDecls@@ -64,10 +59,10 @@                        }                    ]              in-               DSDocumentSymbols (List allSymbols)+               InL (List allSymbols)  -    Nothing -> pure $ Right $ DSDocumentSymbols (List [])+    Nothing -> pure $ Right $ InL (List [])  documentSymbolForDecl :: Located (HsDecl GhcPs) -> Maybe DocumentSymbol documentSymbolForDecl (L (RealSrcSpan l) (TyClD _ FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } }))
− src/Development/IDE/LSP/Protocol.hs
@@ -1,23 +0,0 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.--- SPDX-License-Identifier: Apache-2.0-{-# LANGUAGE PatternSynonyms #-}--module Development.IDE.LSP.Protocol-    ( pattern EventFileDiagnostics-    ) where--import Development.IDE.Types.Diagnostics-import Development.IDE.Types.Location-import Language.Haskell.LSP.Messages-import Language.Haskell.LSP.Types--------------------------------------------------------------------------------------------------------- Pretty printing--------------------------------------------------------------------------------------------------------- | Pattern synonym to make it a bit more convenient to match on diagnostics--- in things like damlc test.-pattern EventFileDiagnostics :: FilePath -> [Diagnostic] -> FromServerMessage-pattern EventFileDiagnostics fp diags <--    NotPublishDiagnostics-        (NotificationMessage _ _ (PublishDiagnosticsParams (uriToFilePath' -> Just fp) (List diags)))
src/Development/IDE/LSP/Server.hs view
@@ -5,81 +5,57 @@  {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE RankNTypes #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE GADTs #-} module Development.IDE.LSP.Server-  ( WithMessage(..)-  , PartialHandlers(..)-  , HasTracing(..)-  ,setUriAnd) where+  ( ReactorMessage(..)+  , ReactorChan+  , ServerM+  , requestHandler+  , notificationHandler+  ) where +import Language.LSP.Server (LspM, Handlers)+import Language.LSP.Types+import qualified Language.LSP.Server as LSP+import Development.IDE.Core.Shake+import UnliftIO.Chan+import Control.Monad.Reader+import Ide.Types (HasTracing, traceWithSpan)+import Development.IDE.Core.Tracing -import Control.Lens ((^.))-import Data.Default+data ReactorMessage+  = ReactorNotification (IO ())+  | ReactorRequest SomeLspId (IO ()) (ResponseError -> IO ()) -import           Language.Haskell.LSP.Types-import qualified Language.Haskell.LSP.Core as LSP-import qualified Language.Haskell.LSP.Messages as LSP-import Language.Haskell.LSP.Types.Lens (HasTextDocument (textDocument), HasUri (uri))-import Development.IDE.Core.Service-import Data.Aeson (Value)-import Development.IDE.Core.Tracing (otSetUri)-import OpenTelemetry.Eventlog (SpanInFlight, setTag)-import Data.Text.Encoding (encodeUtf8)+type ReactorChan = Chan ReactorMessage+type ServerM c = ReaderT (ReactorChan, IdeState) (LspM c) -data WithMessage c = WithMessage-    {withResponse :: forall m req resp . (Show m, Show req, HasTracing req) =>-        (ResponseMessage resp -> LSP.FromServerMessage) -> -- how to wrap a response-        (LSP.LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp)) -> -- actual work-        Maybe (LSP.Handler (RequestMessage m req resp))-    ,withNotification :: forall m req . (Show m, Show req, HasTracing req) =>-        Maybe (LSP.Handler (NotificationMessage m req)) -> -- old notification handler-        (LSP.LspFuncs c -> IdeState -> req -> IO ()) -> -- actual work-        Maybe (LSP.Handler (NotificationMessage m req))-    ,withResponseAndRequest :: forall m rm req resp newReqParams newReqBody .-        (Show m, Show rm, Show req, Show newReqParams, Show newReqBody, HasTracing req) =>-        (ResponseMessage resp -> LSP.FromServerMessage) -> -- how to wrap a response-        (RequestMessage rm newReqParams newReqBody -> LSP.FromServerMessage) -> -- how to wrap the additional req-        (LSP.LspFuncs c -> IdeState -> req -> IO (Either ResponseError resp, Maybe (rm, newReqParams))) -> -- actual work-        Maybe (LSP.Handler (RequestMessage m req resp))-    , withInitialize :: (LSP.LspFuncs c -> IdeState -> InitializeParams -> IO ())-                     -> Maybe (LSP.Handler InitializeRequest)-    }+requestHandler+  :: forall (m :: Method FromClient Request) c. (HasTracing (MessageParams m)) =>+     SMethod m+  -> (IdeState -> MessageParams m -> LspM c (Either ResponseError (ResponseResult m)))+  -> Handlers (ServerM c)+requestHandler m k = LSP.requestHandler m $ \RequestMessage{_method,_id,_params} resp -> do+  st@(chan,ide) <- ask+  env <- LSP.getLspEnv+  let resp' = flip runReaderT st . resp+      trace x = otTracedHandler "Request" (show _method) $ \sp -> do+        traceWithSpan sp _params+        x+  writeChan chan $ ReactorRequest (SomeLspId _id) (trace $ LSP.runLspT env $ resp' =<< k ide _params) (LSP.runLspT env . resp' . Left) -newtype PartialHandlers c = PartialHandlers (WithMessage c -> LSP.Handlers -> IO LSP.Handlers)+notificationHandler+  :: forall (m :: Method FromClient Notification) c. (HasTracing (MessageParams m)) =>+     SMethod m+  -> (IdeState -> MessageParams m -> LspM c ())+  -> Handlers (ServerM c)+notificationHandler m k = LSP.notificationHandler m $ \NotificationMessage{_params,_method}-> do+  (chan,ide) <- ask+  env <- LSP.getLspEnv+  let trace x = otTracedHandler "Notification" (show _method) $ \sp -> do+        traceWithSpan sp _params+        x+  writeChan chan $ ReactorNotification (trace $ LSP.runLspT env $ k ide _params) -instance Default (PartialHandlers c) where-    def = PartialHandlers $ \_ x -> pure x -instance Semigroup (PartialHandlers c) where-    PartialHandlers a <> PartialHandlers b = PartialHandlers $ \w x -> a w x >>= b w--instance Monoid (PartialHandlers c) where-    mempty = def--class HasTracing a where-  traceWithSpan :: SpanInFlight -> a -> IO ()-  traceWithSpan _ _ = pure ()--instance {-# OVERLAPPABLE #-} (HasTextDocument a doc, HasUri doc Uri) => HasTracing a where-  traceWithSpan sp a = otSetUri sp (a ^. textDocument . uri)--instance HasTracing Value-instance HasTracing ExecuteCommandParams-instance HasTracing DidChangeWatchedFilesParams-instance HasTracing DidChangeWorkspaceFoldersParams-instance HasTracing DidChangeConfigurationParams-instance HasTracing InitializeParams-instance HasTracing (Maybe InitializedParams)-instance HasTracing WorkspaceSymbolParams where-  traceWithSpan sp (WorkspaceSymbolParams query _) = setTag sp "query" (encodeUtf8 query)--setUriAnd ::-  (HasTextDocument params a, HasUri a Uri) =>-  (lspFuncs -> ide -> params -> IO res) ->-  lspFuncs ->-  SpanInFlight ->-  ide ->-  params ->-  IO res-setUriAnd k lf sp ide params = do-  otSetUri sp (params ^. textDocument . uri)-  k lf ide params
+ src/Development/IDE/Main.hs view
@@ -0,0 +1,224 @@+module Development.IDE.Main (Arguments(..), defaultMain) where+import Control.Concurrent.Extra (readVar)+import Control.Exception.Safe (+    Exception (displayException),+    catchAny,+ )+import Control.Monad.Extra (concatMapM, unless, when)+import Data.Default (Default (def))+import qualified Data.HashMap.Strict as HashMap+import Data.List.Extra (+    intercalate,+    isPrefixOf,+    nub,+    nubOrd,+    partition,+ )+import Data.Maybe (catMaybes, fromMaybe, isJust)+import qualified Data.Text as T+import Development.IDE (Action, Rules, noLogging)+import Development.IDE.Core.Debouncer (newAsyncDebouncer)+import Development.IDE.Core.FileStore (makeVFSHandle)+import Development.IDE.Core.OfInterest (+    FileOfInterestStatus (OnDisk),+    kick,+    setFilesOfInterest,+ )+import Development.IDE.Core.RuleTypes (+    GenerateCore (GenerateCore),+    GetHieAst (GetHieAst),+    GhcSession (GhcSession),+    GhcSessionDeps (GhcSessionDeps),+    TypeCheck (TypeCheck),+ )+import Development.IDE.Core.Rules (+    GhcSessionIO (GhcSessionIO),+    mainRule,+ )+import Development.IDE.Core.Service (initialise, runAction)+import Development.IDE.Core.Shake (+    IdeState (shakeExtras),+    ShakeExtras (state),+    uses,+ )+import Development.IDE.Core.Tracing (measureMemory)+import Development.IDE.LSP.LanguageServer (runLanguageServer)+import Development.IDE.Plugin (+    Plugin (pluginHandlers, pluginRules),+ )+import Development.IDE.Plugin.HLS (asGhcIdePlugin)+import Development.IDE.Session (SessionLoadingOptions, loadSessionWithOptions, setInitialDynFlags, getHieDbLoc, runWithDb)+import Development.IDE.Types.Location (toNormalizedFilePath')+import Development.IDE.Types.Logger (Logger)+import Development.IDE.Types.Options (+    IdeGhcSession,+    IdeOptions (optCheckParents, optCheckProject, optReportProgress),+    clientSupportsProgress,+    defaultIdeOptions,+ )+import Development.IDE.Types.Shake (Key (Key))+import Development.Shake (action)+import HIE.Bios.Cradle (findCradle)+import Ide.Plugin.Config (CheckParents (NeverCheck), Config, getConfigFromNotification)+import Ide.PluginUtils (allLspCmdIds', getProcessID, pluginDescToIdePlugins)+import Ide.Types (IdePlugins)+import qualified Language.LSP.Server as LSP+import qualified System.Directory.Extra as IO+import System.Exit (ExitCode (ExitFailure), exitWith)+import System.FilePath (takeExtension, takeFileName)+import System.IO (hPutStrLn, hSetEncoding, stderr, stdout, utf8)+import System.Time.Extra (offsetTime, showDuration)+import Text.Printf (printf)+import qualified Development.IDE.Plugin.HLS.GhcIde as Ghcide++data Arguments = Arguments+    { argsOTMemoryProfiling :: Bool+    , argFiles :: Maybe [FilePath]   -- ^ Nothing: lsp server ;  Just: typecheck and exit+    , argsLogger :: Logger+    , argsRules :: Rules ()+    , argsHlsPlugins :: IdePlugins IdeState+    , argsGhcidePlugin :: Plugin Config  -- ^ Deprecated+    , argsSessionLoadingOptions :: SessionLoadingOptions+    , argsIdeOptions :: Maybe Config -> Action IdeGhcSession -> IdeOptions+    , argsLspOptions :: LSP.Options+    , argsDefaultHlsConfig :: Config+    , argsGetHieDbLoc :: FilePath -> IO FilePath -- ^ Map project roots to the location of the hiedb for the project+    }++instance Default Arguments where+    def = Arguments+        { argsOTMemoryProfiling = False+        , argFiles = Nothing+        , argsLogger = noLogging+        , argsRules = mainRule >> action kick+        , argsGhcidePlugin = mempty+        , argsHlsPlugins = pluginDescToIdePlugins Ghcide.descriptors+        , argsSessionLoadingOptions = def+        , argsIdeOptions = const defaultIdeOptions+        , argsLspOptions = def {LSP.completionTriggerCharacters = Just "."}+        , argsDefaultHlsConfig = def+        , argsGetHieDbLoc = getHieDbLoc+        }++defaultMain :: Arguments -> IO ()+defaultMain Arguments{..} = do+    pid <- T.pack . show <$> getProcessID++    let hlsPlugin = asGhcIdePlugin argsHlsPlugins+        hlsCommands = allLspCmdIds' pid argsHlsPlugins+        plugins = hlsPlugin <> argsGhcidePlugin+        options = argsLspOptions { LSP.executeCommandCommands = Just hlsCommands }+        argsOnConfigChange _ide = pure . getConfigFromNotification argsDefaultHlsConfig+        rules = argsRules >> pluginRules plugins++    case argFiles of+        Nothing -> do+            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 argsGetHieDbLoc argsOnConfigChange (pluginHandlers plugins) $ \env vfs rootPath hiedb hieChan -> do+                t <- t+                hPutStrLn stderr $ "Started LSP server in " ++ showDuration t++                dir <- IO.getCurrentDirectory++                -- We want to set the global DynFlags right now, so that we can use+                -- `unsafeGlobalDynFlags` even before the project is configured+                -- We do it here since haskell-lsp changes our working directory to the correct place ('rootPath')+                -- before calling this function+                _mlibdir <-+                    setInitialDynFlags argsSessionLoadingOptions+                        `catchAny` (\e -> (hPutStrLn stderr $ "setInitialDynFlags: " ++ displayException e) >> pure Nothing)++                sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions $ fromMaybe dir rootPath+                config <- LSP.runLspT env LSP.getConfig+                let options = (argsIdeOptions config sessionLoader)+                            { optReportProgress = clientSupportsProgress caps+                            }+                    caps = LSP.resClientCapabilities env+                debouncer <- newAsyncDebouncer+                initialise+                    rules+                    (Just env)+                    argsLogger+                    debouncer+                    options+                    vfs+                    hiedb+                    hieChan+        Just argFiles -> do+          dir <- IO.getCurrentDirectory+          dbLoc <- getHieDbLoc dir+          runWithDb dbLoc $ \hiedb hieChan -> do+            -- GHC produces messages with UTF8 in them, so make sure the terminal doesn't error+            hSetEncoding stdout utf8+            hSetEncoding stderr utf8++            putStrLn $ "ghcide setup tester in " ++ dir ++ "."+            putStrLn "Report bugs at https://github.com/haskell/haskell-language-server/issues"++            putStrLn $ "\nStep 1/4: Finding files to test in " ++ dir+            files <- expandFiles (argFiles ++ ["." | null argFiles])+            -- LSP works with absolute file paths, so try and behave similarly+            files <- nubOrd <$> mapM IO.canonicalizePath files+            putStrLn $ "Found " ++ show (length files) ++ " files"++            putStrLn "\nStep 2/4: Looking for hie.yaml files that control setup"+            cradles <- mapM findCradle files+            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+            sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions dir+            let options = (argsIdeOptions Nothing sessionLoader)+                        { optCheckParents = pure NeverCheck+                        , optCheckProject = pure False+                        }+            ide <- initialise rules Nothing argsLogger debouncer options vfs hiedb hieChan++            putStrLn "\nStep 4/4: Type checking the files"+            setFilesOfInterest ide $ HashMap.fromList $ map ((,OnDisk) . toNormalizedFilePath') files+            results <- runAction "User TypeCheck" ide $ uses TypeCheck (map toNormalizedFilePath' files)+            _results <- runAction "GetHie" ide $ uses GetHieAst (map toNormalizedFilePath' files)+            _results <- runAction "GenerateCore" ide $ uses GenerateCore (map toNormalizedFilePath' files)+            let (worked, failed) = partition fst $ zip (map isJust results) files+            when (failed /= []) $+                putStr $ unlines $ "Files that failed:" : map ((++) " * " . snd) failed++            let nfiles xs = let n = length xs in if n == 1 then "1 file" else show n ++ " files"+            putStrLn $ "\nCompleted (" ++ nfiles worked ++ " worked, " ++ nfiles failed ++ " failed)"++            when argsOTMemoryProfiling $ do+                let valuesRef = state $ shakeExtras ide+                values <- readVar valuesRef+                let consoleObserver Nothing = return $ \size -> printf "Total: %.2fMB\n" (fromIntegral @Int @Double size / 1e6)+                    consoleObserver (Just k) = return $ \size -> printf "  - %s: %.2fKB\n" (show k) (fromIntegral @Int @Double size / 1e3)++                printf "# Shake value store contents(%d):\n" (length values)+                let keys =+                        nub $+                            Key GhcSession :+                            Key GhcSessionDeps :+                            [k | (_, k) <- HashMap.keys values, k /= Key GhcSessionIO]+                            ++ [Key GhcSessionIO]+                measureMemory argsLogger [keys] consoleObserver valuesRef++            unless (null failed) (exitWith $ ExitFailure (length failed))+{-# ANN defaultMain ("HLint: ignore Use nubOrd" :: String) #-}++expandFiles :: [FilePath] -> IO [FilePath]+expandFiles = concatMapM $ \x -> do+    b <- IO.doesFileExist x+    if b+        then return [x]+        else do+            let recurse "." = True+                recurse x | "." `isPrefixOf` takeFileName x = False -- skip .git etc+                recurse x = takeFileName x `notElem` ["dist", "dist-newstyle"] -- cabal directories+            files <- filter (\x -> takeExtension x `elem` [".hs", ".lhs"]) <$> IO.listFilesInside (return . recurse) x+            when (null files) $+                fail $ "Couldn't find any .hs/.lhs files inside directory: " ++ x+            return files
src/Development/IDE/Plugin.hs view
@@ -1,22 +1,21 @@-module Development.IDE.Plugin-  ( Plugin(..)-  ) where+module Development.IDE.Plugin ( Plugin(..) ) where  import Data.Default import Development.Shake-import Development.IDE.LSP.Server +import Development.IDE.LSP.Server+import qualified Language.LSP.Server as LSP  data Plugin c = Plugin     {pluginRules :: Rules ()-    ,pluginHandler :: PartialHandlers c+    ,pluginHandlers :: LSP.Handlers (ServerM c)     }  instance Default (Plugin c) where-    def = Plugin mempty def+    def = Plugin mempty mempty  instance Semigroup (Plugin c) where-    Plugin x1 y1 <> Plugin x2 y2 = Plugin (x1<>x2) (y1<>y2)+    Plugin x1 h1 <> Plugin x2 h2 = Plugin (x1<>x2) (h1 <> h2)  instance Monoid (Plugin c) where     mempty = def
src/Development/IDE/Plugin/CodeAction.hs view
@@ -2,7 +2,9 @@ -- SPDX-License-Identifier: Apache-2.0  {-# LANGUAGE DuplicateRecordFields #-}-{-# LANGUAGE CPP                   #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE GADTs #-} #include "ghc-api-version.h"  -- | Go to the definition of a variable.@@ -14,6 +16,7 @@     ) where  import Control.Monad (join, guard)+import Control.Monad.IO.Class import Development.IDE.GHC.Compat import Development.IDE.Core.Rules import Development.IDE.Core.RuleTypes@@ -29,9 +32,9 @@ import Development.IDE.Types.Location import Development.IDE.Types.Options import qualified Data.HashMap.Strict as Map-import qualified Language.Haskell.LSP.Core as LSP-import Language.Haskell.LSP.VFS-import Language.Haskell.LSP.Types+import qualified Language.LSP.Server as LSP+import Language.LSP.VFS+import Language.LSP.Types import qualified Data.Rope.UTF16 as Rope import Data.Char import Data.Maybe@@ -58,42 +61,49 @@ import qualified GHC.LanguageExtensions as Lang import Control.Lens (alaf) import Data.Monoid (Ap(..))+import TcRnTypes (TcGblEnv(..), ImportAvails(..))+import HscTypes (ImportedModsVal(..), importedByUser)+import RdrName (GlobalRdrElt(..), lookupGlobalRdrEnv)+import SrcLoc (realSrcSpanStart)+import Module (moduleEnvElts)+import qualified Data.Map as M+import qualified Data.Set as S  descriptor :: PluginId -> PluginDescriptor IdeState descriptor plId =   (defaultPluginDescriptor plId)     { pluginRules = mempty,-      pluginCodeActionProvider = Just codeAction+      pluginHandlers = mkPluginHandler STextDocumentCodeAction codeAction     }  -- | Generate code actions. codeAction-    :: LSP.LspFuncs c-    -> IdeState+    :: IdeState     -> PluginId-    -> TextDocumentIdentifier-    -> Range-    -> CodeActionContext-    -> IO (Either ResponseError (List CAResult))-codeAction lsp state _ (TextDocumentIdentifier uri) _range CodeActionContext{_diagnostics=List xs} = do-    contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri+    -> CodeActionParams+    -> LSP.LspM c (Either ResponseError (List (Command |? CodeAction)))+codeAction state _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext{_diagnostics=List xs}) = do+  contents <- LSP.getVirtualFile $ toNormalizedUri uri+  liftIO $ do     let text = Rope.toText . (_text :: VirtualFile -> Rope.Rope) <$> contents         mbFile = toNormalizedFilePath' <$> uriToFilePath uri     diag <- fmap (\(_, _, d) -> d) . filter (\(p, _, _) -> mbFile == Just p) <$> getDiagnostics state-    (ideOptions, join -> parsedModule, join -> env, join -> annotatedPS) <- runAction "CodeAction" state $-      (,,,) <$> getIdeOptions+    (ideOptions, join -> parsedModule, join -> env, join -> annotatedPS, join -> tcM, join -> har) <- runAction "CodeAction" state $+      (,,,,,) <$> getIdeOptions             <*> getParsedModule `traverse` mbFile             <*> use GhcSession `traverse` mbFile             <*> use GetAnnotatedParsedSource `traverse` mbFile+            <*> use TypeCheck `traverse` mbFile+            <*> use GetHieAst `traverse` mbFile     -- This is quite expensive 0.6-0.7s on GHC-    let pkgExports = envPackageExports <$> env+    pkgExports   <- maybe mempty envPackageExports env     localExports <- readVar (exportsMap $ shakeExtras state)     let-      exportsMap = localExports <> fromMaybe mempty pkgExports+      exportsMap = localExports <> pkgExports       df = ms_hspp_opts . pm_mod_summary <$> parsedModule       actions =         [ mkCA title  [x] edit-        | x <- xs, (title, tedit) <- suggestAction exportsMap ideOptions parsedModule text df annotatedPS x+        | x <- xs, (title, tedit) <- suggestAction exportsMap ideOptions parsedModule text df annotatedPS tcM har x         , let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing         ]       actions' = caRemoveRedundantImports parsedModule text diag xs uri@@ -101,9 +111,9 @@                <> caRemoveInvalidExports parsedModule text diag xs uri     pure $ Right $ List actions' -mkCA :: T.Text -> [Diagnostic] -> WorkspaceEdit -> CAResult+mkCA :: T.Text -> [Diagnostic] -> WorkspaceEdit -> (Command |? CodeAction) mkCA title diags edit =-  CACodeAction $ CodeAction title (Just CodeActionQuickFix) (Just $ List diags) (Just edit) Nothing+  InR $ CodeAction title (Just CodeActionQuickFix) (Just $ List diags) Nothing Nothing (Just edit) Nothing  rewrite ::     Maybe DynFlags ->@@ -123,9 +133,11 @@   -> Maybe T.Text   -> Maybe DynFlags   -> Maybe (Annotated ParsedSource)+  -> Maybe TcModuleResult+  -> Maybe HieAstResult   -> Diagnostic   -> [(T.Text, [TextEdit])]-suggestAction packageExports ideOptions parsedModule text df annSource diag =+suggestAction packageExports ideOptions parsedModule text df annSource tcM har diag =     concat    -- Order these suggestions by priority     [ suggestSignature True diag@@ -140,6 +152,7 @@     , suggestAddTypeAnnotationToSatisfyContraints text diag     , rewrite df annSource $ \df ps -> suggestConstraint df ps diag     , rewrite df annSource $ \_ ps -> suggestImplicitParameter ps diag+    , rewrite df annSource $ \_ ps -> suggestHideShadow ps tcM har diag     ] ++ concat     [  suggestNewDefinition ideOptions pm text diag     ++ suggestNewImport packageExports pm diag@@ -169,9 +182,84 @@ findDeclContainingLoc :: Position -> [Located a] -> Maybe (Located a) findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` l) +-- Single:+-- This binding for ‘mod’ shadows the existing binding+--   imported from ‘Prelude’ at haskell-language-server/ghcide/src/Development/IDE/Plugin/CodeAction.hs:10:8-40+--   (and originally defined in ‘GHC.Real’)typecheck(-Wname-shadowing)+-- Multi:+--This binding for ‘pack’ shadows the existing bindings+--  imported from ‘Data.ByteString’ at B.hs:6:1-22+--  imported from ‘Data.ByteString.Lazy’ at B.hs:8:1-27+--  imported from ‘Data.Text’ at B.hs:7:1-16+suggestHideShadow :: ParsedSource -> Maybe TcModuleResult -> Maybe HieAstResult -> Diagnostic -> [(T.Text, [Rewrite])]+suggestHideShadow pm@(L _ HsModule {hsmodImports}) mTcM mHar Diagnostic {_message, _range}+  | Just [identifier, modName, s] <-+      matchRegexUnifySpaces+        _message+        "This binding for ‘([^`]+)’ shadows the existing binding imported from ‘([^`]+)’ at ([^ ]*)" =+    suggests identifier modName s+  | Just [identifier] <-+      matchRegexUnifySpaces+        _message+        "This binding for ‘([^`]+)’ shadows the existing bindings",+    Just matched <- allMatchRegexUnifySpaces _message "imported from ‘([^’]+)’ at ([^ ]*)",+    mods <- [(modName, s) | [_, modName, s] <- matched],+    result <- nubOrdBy (compare `on` fst) $ mods >>= uncurry (suggests identifier),+    hideAll <- ("Hide " <> identifier <> " from all occurence imports", concat $ snd <$> result) =+    result <> [hideAll]+  | otherwise = []+  where+    suggests identifier modName s+      | Just tcM <- mTcM,+        Just har <- mHar,+        [s'] <- [x | (x, "") <- readSrcSpan $ T.unpack s],+        isUnusedImportedId tcM har (T.unpack identifier) (T.unpack modName) (RealSrcSpan s'),+        mDecl <- findImportDeclByModuleName hsmodImports $ T.unpack modName,+        title <- "Hide " <> identifier <> " from " <> modName =+        if modName == "Prelude" && null mDecl+          then [(title, maybeToList $ hideImplicitPreludeSymbol (T.unpack identifier) pm)]+          else maybeToList $ (title,) . pure . hideSymbol (T.unpack identifier) <$> mDecl+      | otherwise = []++findImportDeclByModuleName :: [LImportDecl GhcPs] -> String -> Maybe (LImportDecl GhcPs)+findImportDeclByModuleName decls modName = flip find decls $ \case+  (L _ ImportDecl {..}) -> modName == moduleNameString (unLoc ideclName)+  _ -> error "impossible"++isTheSameLine :: SrcSpan -> SrcSpan -> Bool+isTheSameLine s1 s2+  | Just sl1 <- getStartLine s1,+    Just sl2 <- getStartLine s2 =+    sl1 == sl2+  | otherwise = False+  where+    getStartLine x = srcLocLine . realSrcSpanStart <$> realSpan x++isUnusedImportedId :: TcModuleResult -> HieAstResult -> String -> String -> SrcSpan -> Bool+isUnusedImportedId+  TcModuleResult {tmrTypechecked = TcGblEnv {tcg_imports = ImportAvails {imp_mods}}}+  HAR {refMap}+  identifier+  modName+  importSpan+    | occ <- mkVarOcc identifier,+      impModsVals <- importedByUser . concat $ moduleEnvElts imp_mods,+      Just rdrEnv <-+        listToMaybe+          [ imv_all_exports+            | ImportedModsVal {..} <- impModsVals,+              imv_name == mkModuleName modName,+              isTheSameLine imv_span importSpan+          ],+      [GRE {..}] <- lookupGlobalRdrEnv rdrEnv occ,+      importedIdentifier <- Right gre_name,+      refs <- M.lookup importedIdentifier refMap =+      maybe True (not . any (\(_, IdentifierDetails {..}) -> identInfo == S.singleton Use)) refs+    | otherwise = False+ suggestDisableWarning :: ParsedModule -> Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])] suggestDisableWarning pm contents Diagnostic{..}-    | Just (StringValue (T.stripPrefix "-W" -> Just w)) <- _code =+    | Just (InR (T.stripPrefix "-W" -> Just w)) <- _code =         pure             ( "Disable \"" <> w <> "\" warnings"             , [TextEdit (endOfModuleHeader pm contents) $ "{-# OPTIONS_GHC -Wno-" <> w <> " #-}\n"]@@ -197,7 +285,7 @@         = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]     | otherwise = [] -caRemoveRedundantImports :: Maybe ParsedModule -> Maybe T.Text -> [Diagnostic] -> [Diagnostic] -> Uri -> [CAResult]+caRemoveRedundantImports :: Maybe ParsedModule -> Maybe T.Text -> [Diagnostic] -> [Diagnostic] -> Uri -> [Command |? CodeAction] caRemoveRedundantImports m contents digs ctxDigs uri   | Just pm <- m,     r <- join $ map (\d -> repeat d `zip` suggestRemoveRedundantImport pm contents d) digs,@@ -212,16 +300,18 @@     removeSingle title tedit diagnostic = mkCA title [diagnostic] WorkspaceEdit{..} where         _changes = Just $ Map.singleton uri $ List tedit         _documentChanges = Nothing-    removeAll tedit = CACodeAction CodeAction {..} where+    removeAll tedit = InR $ CodeAction{..} where         _changes = Just $ Map.singleton uri $ List tedit         _title = "Remove all redundant imports"         _kind = Just CodeActionQuickFix         _diagnostics = Nothing         _documentChanges = Nothing         _edit = Just WorkspaceEdit{..}+        _isPreferred = Nothing         _command = Nothing+        _disabled = Nothing -caRemoveInvalidExports :: Maybe ParsedModule -> Maybe T.Text -> [Diagnostic] -> [Diagnostic] -> Uri -> [CAResult]+caRemoveInvalidExports :: Maybe ParsedModule -> Maybe T.Text -> [Diagnostic] -> [Diagnostic] -> Uri -> [Command |? CodeAction] caRemoveInvalidExports m contents digs ctxDigs uri   | Just pm <- m,     Just txt <- contents,@@ -245,7 +335,7 @@       | otherwise = Nothing      removeSingle (_, _, []) = Nothing-    removeSingle (title, diagnostic, ranges) = Just $ CACodeAction CodeAction{..} where+    removeSingle (title, diagnostic, ranges) = Just $ InR $ CodeAction{..} where         tedit = concatMap (\r -> [TextEdit r ""]) $ nubOrd ranges         _changes = Just $ Map.singleton uri $ List tedit         _title = title@@ -254,8 +344,10 @@         _documentChanges = Nothing         _edit = Just WorkspaceEdit{..}         _command = Nothing+        _isPreferred = Nothing+        _disabled = Nothing     removeAll [] = Nothing-    removeAll ranges = Just $ CACodeAction CodeAction {..} where+    removeAll ranges = Just $ InR $ CodeAction{..} where         tedit = concatMap (\r -> [TextEdit r ""]) ranges         _changes = Just $ Map.singleton uri $ List tedit         _title = "Remove all redundant exports"@@ -264,6 +356,8 @@         _documentChanges = Nothing         _edit = Just WorkspaceEdit{..}         _command = Nothing+        _isPreferred = Nothing+        _disabled = Nothing  suggestRemoveRedundantExport :: ParsedModule -> Diagnostic -> Maybe (T.Text, [Range]) suggestRemoveRedundantExport ParsedModule{pm_parsed_source = L _ HsModule{..}} Diagnostic{..}@@ -614,7 +708,7 @@       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+        haskell-lsp-types-0.22.0.0:Language.LSP.Types.Window.$sel:_value:ProgressParams (_ :: ProgressParams                                                                                                         LSP.Handlers)         T.foldl (_ :: LSP.Handlers -> Char -> LSP.Handlers)                 (_ :: LSP.Handlers)
src/Development/IDE/Plugin/CodeAction/ExactPrint.hs view
@@ -31,18 +31,18 @@ import Development.IDE.GHC.ExactPrint     ( Annotate, ASTElement(parseAST) ) import FieldLabel (flLabel)-import GhcPlugins (sigPrec)+import GhcPlugins (sigPrec, mkRealSrcLoc) import Language.Haskell.GHC.ExactPrint import Language.Haskell.GHC.ExactPrint.Types (DeltaPos (DP), KeywordId (G), mkAnnKey)-import Language.Haskell.LSP.Types+import Language.LSP.Types import OccName import Outputable (ppr, showSDocUnsafe, showSDoc) import Retrie.GHC (rdrNameOcc, unpackFS, mkRealSrcSpan, realSrcSpanEnd) import Development.IDE.Spans.Common import Development.IDE.GHC.Error-import Safe (lastMay) import Data.Generics (listify) import GHC.Exts (IsList (fromList))+import Control.Monad.Extra (whenJust)  ------------------------------------------------------------------------------ @@ -205,6 +205,7 @@ -- extendImportTopLevel "foo" AST: -- -- import A --> Error+-- import A (foo) --> Error -- import A (bar) --> import A (bar, foo) extendImportTopLevel :: DynFlags -> String -> LImportDecl GhcPs -> TransformT (Either String) (LImportDecl GhcPs) extendImportTopLevel df idnetifier (L l it@ImportDecl {..})@@ -382,6 +383,8 @@         lidecl' = L l $ idecl             { ideclHiding = Just (False, edited)             }+    -- avoid import A (foo,)+    whenJust (lastMaybe deletedLies) removeTrailingCommaT     when (not (null lies) && null deletedLies) $ do         transferAnn llies edited id         addSimpleAnnT edited dp00@@ -408,13 +411,16 @@                   (filter ((/= symbol) . T.pack . unpackFS . flLabel . unLoc) flds)         killLie v = Just v +-- | Insert a import declaration hiding a symbole from Prelude hideImplicitPreludeSymbol     :: String -> ParsedSource -> Maybe Rewrite hideImplicitPreludeSymbol symbol (L _ HsModule{..}) = do-    existingImp <- lastMay hsmodImports-    exisImpSpan <- realSpan $ getLoc existingImp-    let indentation = srcSpanStartCol exisImpSpan-        beg = realSrcSpanEnd exisImpSpan+    let predLine old = mkRealSrcLoc (srcLocFile old) (srcLocLine old - 1) (srcLocCol old)+        existingImpSpan =  (fmap (id,) . realSpan . getLoc) =<< lastMaybe hsmodImports+        existingDeclSpan = (fmap (predLine, ) . realSpan . getLoc) =<< headMaybe hsmodDecls+    (f, s) <- existingImpSpan <|> existingDeclSpan+    let beg = f $ realSrcSpanEnd s+        indentation = srcSpanStartCol s         ran = RealSrcSpan $ mkRealSrcSpan beg beg     pure $ Rewrite ran $ \df -> do         let symOcc = mkVarOcc symbol@@ -424,6 +430,6 @@         -- Re-labeling is needed to reflect annotations correctly         L _ idecl0 <- liftParseAST @(ImportDecl GhcPs) df $ T.unpack impStmt         let idecl = L ran idecl0-        addSimpleAnnT idecl (DP (1,indentation - 1))+        addSimpleAnnT idecl (DP (1, indentation - 1))             [(G AnnImport, DP (1, indentation - 1))]         pure idecl
src/Development/IDE/Plugin/CodeAction/PositionIndexed.hs view
@@ -12,7 +12,7 @@  import           Data.Char import           Data.List-import           Language.Haskell.LSP.Types+import           Language.LSP.Types  type PositionIndexed a = [(Position, a)] 
src/Development/IDE/Plugin/Completions.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE CPP          #-}+{-# LANGUAGE RankNTypes   #-} {-# LANGUAGE TypeFamilies #-} #include "ghc-api-version.h" @@ -7,16 +8,17 @@     , LocalCompletions(..)     , NonLocalCompletions(..)     ) where-import Language.Haskell.LSP.Types-import qualified Language.Haskell.LSP.Core as LSP-import qualified Language.Haskell.LSP.VFS as VFS  import Control.Monad+import Control.Monad.Extra import Control.Monad.Trans.Maybe import Data.Aeson import Data.List (find) import Data.Maybe import qualified Data.Text as T+import Language.LSP.Types+import qualified Language.LSP.Server as LSP+import qualified Language.LSP.VFS as VFS import Development.Shake.Classes import Development.Shake import GHC.Generics@@ -36,16 +38,16 @@ import Ide.Types import TcRnDriver (tcRnImportDecls) import Control.Concurrent.Async (concurrently)-#if defined(GHC_LIB)-import Development.IDE.Import.DependencyInformation-#endif+import GHC.Exts (toList)+import Development.IDE.GHC.Error (rangeToSrcSpan)+import Development.IDE.GHC.Util (prettyPrint)  descriptor :: PluginId -> PluginDescriptor IdeState descriptor plId = (defaultPluginDescriptor plId)-    { pluginRules = produceCompletions,-      pluginCompletionProvider = Just (getCompletionsLSP plId),-      pluginCommands = [extendImportCommand]-    }+  { pluginRules = produceCompletions+  , pluginHandlers = mkPluginHandler STextDocumentCompletion getCompletionsLSP+  , pluginCommands = [extendImportCommand]+  }  produceCompletions :: Rules () produceCompletions = do@@ -64,15 +66,6 @@         ms <- fmap fst <$> useWithStale GetModSummaryWithoutTimestamps file         sess <- fmap fst <$> useWithStale GhcSessionDeps file --- When possible, rely on the haddocks embedded in our interface files--- This creates problems on ghc-lib, see comment on 'getDocumentationTryGhc'-#if !defined(GHC_LIB)-        let parsedDeps = []-#else-        deps <- maybe (TransitiveDependencies [] [] []) fst <$> useWithStale GetDependencies file-        parsedDeps <- mapMaybe (fmap fst) <$> usesWithStale GetParsedModule (transitiveModuleDeps deps)-#endif-         case (ms, sess) of             (Just (ms,imps), Just sess) -> do               let env = hscEnv sess@@ -81,7 +74,7 @@               case (global, inScope) of                   ((_, Just globalEnv), (_, Just inScopeEnv)) -> do                       let uri = fromNormalizedUri $ normalizedFilePathToUri file-                      cdata <- liftIO $ cacheDataProducer uri env (ms_mod ms) globalEnv inScopeEnv imps parsedDeps+                      cdata <- liftIO $ cacheDataProducer uri sess (ms_mod ms) globalEnv inScopeEnv imps                       return ([], Just cdata)                   (_diag, _) ->                       return ([], Nothing)@@ -115,20 +108,19 @@  -- | Generate code actions. getCompletionsLSP-    :: PluginId-    -> LSP.LspFuncs Config-    -> IdeState+    :: IdeState+    -> PluginId     -> CompletionParams-    -> IO (Either ResponseError CompletionResponseResult)-getCompletionsLSP plId lsp ide+    -> LSP.LspM Config (Either ResponseError (ResponseResult TextDocumentCompletion))+getCompletionsLSP ide plId   CompletionParams{_textDocument=TextDocumentIdentifier uri                   ,_position=position                   ,_context=completionContext} = do-    contents <- LSP.getVirtualFileFunc lsp $ toNormalizedUri uri+    contents <- LSP.getVirtualFile $ toNormalizedUri uri     fmap Right $ case (contents, uriToFilePath' uri) of       (Just cnts, Just path) -> do         let npath = toNormalizedFilePath' path-        (ideOpts, compls) <- runIdeAction "Completion" (shakeExtras ide) $ do+        (ideOpts, compls) <- liftIO $ runIdeAction "Completion" (shakeExtras ide) $ do             opts <- liftIO $ getIdeOptionsIO $ shakeExtras ide             localCompls <- useWithStaleFast LocalCompletions npath             nonLocalCompls <- useWithStaleFast NonLocalCompletions npath@@ -140,16 +132,16 @@             pfix <- VFS.getCompletionPrefix position cnts             case (pfix, completionContext) of               (Just (VFS.PosPrefixInfo _ "" _ _), Just CompletionContext { _triggerCharacter = Just "."})-                -> return (Completions $ List [])+                -> return (InL $ List [])               (Just pfix', _) -> do                 let clientCaps = clientCapabilities $ shakeExtras ide-                config <- getClientConfig lsp+                config <- getClientConfig                 let snippets = WithSnippets . completionSnippetsOn $ config-                allCompletions <- getCompletions plId ideOpts cci' parsedMod bindMap pfix' clientCaps snippets-                pure $ Completions (List allCompletions)-              _ -> return (Completions $ List [])-          _ -> return (Completions $ List [])-      _ -> return (Completions $ List [])+                allCompletions <- liftIO $ getCompletions plId ideOpts cci' parsedMod bindMap pfix' clientCaps snippets+                pure $ InL (List allCompletions)+              _ -> return (InL $ List [])+          _ -> return (InL $ List [])+      _ -> return (InL $ List [])  ---------------------------------------------------------------------------------------------------- @@ -158,16 +150,29 @@   PluginCommand (CommandId extendImportCommandId) "additional edits for a completion" extendImportHandler  extendImportHandler :: CommandFunction IdeState ExtendImport-extendImportHandler _lsp ideState edit = do-  res <- runMaybeT $ extendImportHandler' ideState edit-  return (Right Null, res)+extendImportHandler ideState edit@ExtendImport {..} = do+  res <- liftIO $ runMaybeT $ extendImportHandler' ideState edit+  whenJust res $ \(nfp, wedit@WorkspaceEdit {_changes}) -> do+    let (_, List (head -> TextEdit {_range})) = fromJust $ _changes >>= listToMaybe . toList+        srcSpan = rangeToSrcSpan nfp _range+    LSP.sendNotification SWindowShowMessage $+      ShowMessageParams MtInfo $+        "Import "+          <> maybe ("‘" <> newThing) (\x -> "‘" <> x <> " (" <> newThing <> ")") thingParent+          <> "’ from "+          <> importName+          <> " (at "+          <> T.pack (prettyPrint srcSpan)+          <> ")"+    void $ LSP.sendRequest SWorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing wedit) (\_ -> pure ())+  return $ Right Null -extendImportHandler' :: IdeState -> ExtendImport -> MaybeT IO (ServerMethod, ApplyWorkspaceEditParams)+extendImportHandler' :: IdeState -> ExtendImport -> MaybeT IO (NormalizedFilePath, WorkspaceEdit) extendImportHandler' ideState ExtendImport {..}   | Just fp <- uriToFilePath doc,     nfp <- toNormalizedFilePath' fp =     do-      (ms, ps, imps) <- MaybeT $+      (ms, ps, imps) <- MaybeT $ liftIO $         runAction "extend import" ideState $           runMaybeT $ do             -- We want accurate edits, so do not use stale data here@@ -178,11 +183,9 @@           wantedModule = mkModuleName (T.unpack importName)           wantedQual = mkModuleName . T.unpack <$> importQual       imp <- liftMaybe $ find (isWantedModule wantedModule wantedQual) imps-      wedit <--        liftEither $-          rewriteToWEdit df doc (annsA ps) $-            extendImport (T.unpack <$> thingParent) (T.unpack newThing) imp-      return (WorkspaceApplyEdit, ApplyWorkspaceEditParams wedit)+      fmap (nfp,) $ liftEither $+        rewriteToWEdit df doc (annsA ps) $+          extendImport (T.unpack <$> thingParent) (T.unpack newThing) imp   | otherwise =     mzero 
src/Development/IDE/Plugin/Completions/Logic.hs view
@@ -27,16 +27,15 @@ import Name import RdrName import Type-import Packages #if MIN_GHC_API_VERSION(8,10,0) import Predicate (isDictTy) import Pair import Coercion #endif -import Language.Haskell.LSP.Types-import Language.Haskell.LSP.Types.Capabilities-import qualified Language.Haskell.LSP.VFS as VFS+import Language.LSP.Types+import Language.LSP.Types.Capabilities+import qualified Language.LSP.VFS as VFS import Development.IDE.Core.Compile import Development.IDE.Core.PositionMapping import Development.IDE.Plugin.Completions.Types@@ -59,6 +58,7 @@ import Ide.PluginUtils (mkLspCommand) import Ide.Types (CommandId (..), PluginId, WithSnippets (..)) import Control.Monad+import Development.IDE.Types.HscEnvEq  -- From haskell-ide-engine/hie-plugin-api/Haskell/Ide/Engine/Context.hs @@ -203,16 +203,16 @@                         T.intercalate sectionSeparator docs'  mkAdditionalEditsCommand :: PluginId -> ExtendImport -> IO Command-mkAdditionalEditsCommand pId edits =+mkAdditionalEditsCommand pId edits = pure $   mkLspCommand pId (CommandId extendImportCommandId) "extend import" (Just [toJSON edits]) -mkNameCompItem :: Uri -> Maybe T.Text -> Name -> ModuleName -> Maybe Type -> Maybe Backtick -> SpanDoc -> Maybe (LImportDecl GhcPs) -> CompItem+mkNameCompItem :: Uri -> Maybe T.Text -> OccName -> ModuleName -> Maybe Type -> Maybe Backtick -> SpanDoc -> Maybe (LImportDecl GhcPs) -> CompItem mkNameCompItem doc thingParent origName origMod thingType isInfix docs !imp = CI {..}   where-    compKind = occNameToComKind typeText $ occName origName+    compKind = occNameToComKind typeText origName     importedFrom = Right $ showModName origMod-    isTypeCompl = isTcOcc $ occName origName-    label = showGhc origName+    isTypeCompl = isTcOcc origName+    label = stripPrefix $ showGhc origName     insertText = case isInfix of             Nothing -> case getArgText <$> thingType of                             Nothing -> label@@ -294,9 +294,10 @@     Nothing Nothing Nothing Nothing Nothing  -cacheDataProducer :: Uri -> HscEnv -> Module -> GlobalRdrEnv-> GlobalRdrEnv -> [LImportDecl GhcPs] -> [ParsedModule] -> IO CachedCompletions-cacheDataProducer uri packageState curMod globalEnv inScopeEnv limports deps = do-  let dflags = hsc_dflags packageState+cacheDataProducer :: Uri -> HscEnvEq -> Module -> GlobalRdrEnv-> GlobalRdrEnv -> [LImportDecl GhcPs] -> IO CachedCompletions+cacheDataProducer uri env curMod globalEnv inScopeEnv limports = do+  let+      packageState = hscEnv env       curModName = moduleName curMod        importMap = Map.fromList [ (getLoc imp, imp) | imp <- limports ]@@ -309,8 +310,6 @@       -- Full canonical names of imported modules       importDeclerations = map unLoc limports -      -- The list of all importable Modules from all packages-      moduleNames = map showModName (listVisibleModuleNames dflags)        -- The given namespaces for the imported modules (ie. full name, or alias if used)       allModNamesAsNS = map (showModName . asNamespace) importDeclerations@@ -344,11 +343,11 @@        toCompItem :: Parent -> Module -> ModuleName -> Name -> Maybe (LImportDecl GhcPs) -> IO [CompItem]       toCompItem par m mn n imp' = do-        docs <- getDocumentationTryGhc packageState curMod deps n-        let mbParent = case par of-                            NoParent -> Nothing-                            ParentIs n -> Just (showNameWithoutUniques n)-                            FldParent n _ -> Just (showNameWithoutUniques n)+        docs <- getDocumentationTryGhc packageState curMod n+        let (mbParent, originName) = case par of+                            NoParent -> (Nothing, nameOccName n)+                            ParentIs n' -> (Just $ showNameWithoutUniques n', nameOccName n)+                            FldParent n' lbl -> (Just $ showNameWithoutUniques n', maybe (nameOccName n) mkVarOccFS lbl)         tys <- catchSrcErrors (hsc_dflags packageState) "completion" $ do                 name' <- lookupName packageState m n                 return ( name' >>= safeTyThingType@@ -361,11 +360,14 @@                     [mkRecordSnippetCompItem uri mbParent  ctxStr flds (ppr mn) docs imp']                 _ -> [] -        return $ mkNameCompItem uri mbParent n mn ty Nothing docs imp'+        return $ mkNameCompItem uri mbParent originName mn ty Nothing docs imp'                : recordCompls    (unquals,quals) <- getCompls rdrElts +  -- The list of all importable Modules from all packages+  moduleNames <- maybe [] (map showModName) <$> envVisibleModuleNames env+   return $ CC     { allModNamesAsNS = allModNamesAsNS     , unqualCompls = unquals@@ -588,7 +590,7 @@     -> return $ filtPragmaCompls (pragmaSuffix fullLine)     | otherwise -> do         let uniqueFiltCompls = nubOrdOn insertText filtCompls-        compls <- mapM (mkCompl plId ideOpts . stripAutoGenerated) uniqueFiltCompls+        compls <- mapM (mkCompl plId ideOpts) uniqueFiltCompls         return $ filtModNameCompls               ++ filtKeywordCompls               ++ map ( toggleSnippets caps withSnippets) compls@@ -657,16 +659,11 @@  -- | Under certain circumstance GHC generates some extra stuff that we -- don't want in the autocompleted symbols-stripAutoGenerated :: CompItem -> CompItem-stripAutoGenerated ci =-    ci {label = stripPrefix (label ci)}     {- When e.g. DuplicateRecordFields is enabled, compiler generates     names like "$sel:accessor:One" and "$sel:accessor:Two" to disambiguate record selectors     https://ghc.haskell.org/trac/ghc/wiki/Records/OverloadedRecordFields/DuplicateRecordFields#Implementation     -}- -- TODO: Turn this into an alex lexer that discards prefixes as if they were whitespace.- stripPrefix :: T.Text -> T.Text stripPrefix name = T.takeWhile (/=':') $ go prefixes   where
src/Development/IDE/Plugin/Completions/Types.hs view
@@ -13,7 +13,9 @@ import Data.Aeson (FromJSON, ToJSON) import Data.Text (Text) import GHC.Generics (Generic)-import Language.Haskell.LSP.Types (CompletionItemKind, Uri)+import Language.LSP.Types (CompletionItemKind, Uri)++-- From haskell-ide-engine/src/Haskell/Ide/Engine/LSP/Completions.hs  data Backtick = Surrounded | LeftSide   deriving (Eq, Ord, Show)
src/Development/IDE/Plugin/HLS.hs view
@@ -1,67 +1,55 @@-{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE GADTs #-}  module Development.IDE.Plugin.HLS     (       asGhcIdePlugin     ) where -import           Control.Exception(SomeException, catch)-import           Control.Lens ((^.))+import           Control.Exception(SomeException) import           Control.Monad import qualified Data.Aeson as J-import qualified Data.DList as DList import           Data.Either import qualified Data.List                     as List import qualified Data.Map  as Map-import           Data.Maybe import qualified Data.Text                     as T import           Development.IDE.Core.Shake import           Development.IDE.LSP.Server import           Development.IDE.Plugin-import           Development.IDE.Plugin.HLS.Formatter-import           GHC.Generics import           Ide.Plugin.Config import           Ide.Types as HLS-import qualified Language.Haskell.LSP.Core as LSP-import           Language.Haskell.LSP.Messages-import           Language.Haskell.LSP.Types-import qualified Language.Haskell.LSP.Types              as J-import qualified Language.Haskell.LSP.Types.Capabilities as C-import           Language.Haskell.LSP.Types.Lens as L hiding (formatting, rangeFormatting)-import qualified Language.Haskell.LSP.VFS                as VFS+import qualified Language.LSP.Server             as LSP+import qualified Language.LSP.Types              as J+import Language.LSP.Types import           Text.Regex.TDFA.Text() import Development.Shake (Rules)-import Ide.PluginUtils (getClientConfig, pluginEnabled, getPluginConfig, responseError, getProcessID)+import Ide.PluginUtils (getClientConfig) import Development.IDE.Core.Tracing-import Development.IDE.Types.Logger (logDebug)-import Control.Concurrent.Async (mapConcurrently)+import UnliftIO.Async (forConcurrently)+import UnliftIO.Exception (catchAny)+import           Data.Dependent.Map (DMap)+import qualified Data.Dependent.Map as DMap+import           Data.Dependent.Sum+import Data.List.NonEmpty (nonEmpty,NonEmpty,toList)+import UnliftIO (MonadUnliftIO)+import Data.String+import Data.Bifunctor  -- ---------------------------------------------------------------------+-- --- | Map a set of plugins to the underlying ghcide engine.  Main point is--- IdePlugins are arranged by kind of operation, 'Plugin' is arranged by message--- category ('Notifaction', 'Request' etc).+-- | Map a set of plugins to the underlying ghcide engine. asGhcIdePlugin :: IdePlugins IdeState -> Plugin Config asGhcIdePlugin mp =-    mkPlugin rulesPlugins (Just . HLS.pluginRules) <>-    mkPlugin executeCommandPlugins (Just . pluginCommands) <>-    mkPlugin codeActionPlugins     pluginCodeActionProvider <>-    mkPlugin codeLensPlugins       pluginCodeLensProvider <>-    -- Note: diagnostics are provided via Rules from pluginDiagnosticProvider-    mkPlugin hoverPlugins          pluginHoverProvider <>-    mkPlugin symbolsPlugins        pluginSymbolsProvider <>-    mkPlugin formatterPlugins      pluginFormattingProvider <>-    mkPlugin completionsPlugins    pluginCompletionProvider <>-    mkPlugin renamePlugins         pluginRenameProvider+    mkPlugin rulesPlugins          HLS.pluginRules <>+    mkPlugin executeCommandPlugins HLS.pluginCommands <>+    mkPlugin extensiblePlugins     HLS.pluginHandlers     where-        justs (p, Just x)  = [(p, x)]-        justs (_, Nothing) = []-         ls = Map.toList (ipMap mp) -        mkPlugin :: ([(PluginId, b)] -> Plugin Config) -> (PluginDescriptor IdeState -> Maybe b) -> Plugin Config+        mkPlugin :: ([(PluginId, b)] -> Plugin Config) -> (PluginDescriptor IdeState -> b) -> Plugin Config         mkPlugin maker selector =-          case concatMap (\(pid, p) -> justs (pid, selector p)) ls of+          case map (second selector) ls of             -- If there are no plugins that provide a descriptor, use mempty to             -- create the plugin – otherwise we we end up declaring handlers for             -- capabilities that there are no plugins for@@ -75,418 +63,123 @@     where         rules = foldMap snd rs -codeActionPlugins :: [(PluginId, CodeActionProvider IdeState)] -> Plugin Config-codeActionPlugins cas = Plugin codeActionRules (codeActionHandlers cas)--codeActionRules :: Rules ()-codeActionRules = mempty--codeActionHandlers :: [(PluginId, CodeActionProvider IdeState)] -> PartialHandlers Config-codeActionHandlers cas = PartialHandlers $ \WithMessage{..} x -> return x-    { LSP.codeActionHandler-        = withResponse RspCodeAction (makeCodeAction cas)-    }--makeCodeAction :: [(PluginId, CodeActionProvider IdeState)]-      -> LSP.LspFuncs Config -> IdeState-      -> CodeActionParams-      -> IO (Either ResponseError (List CAResult))-makeCodeAction cas lf ideState (CodeActionParams docId range context _) = do-    let caps = LSP.clientCapabilities lf-        unL (List ls) = ls-        makeAction (pid,provider) = do-          pluginConfig <- getPluginConfig lf pid-          if pluginEnabled pluginConfig plcCodeActionsOn-            then otTracedProvider pid "codeAction" $ provider lf ideState pid docId range context-            else return $ Right (List [])-    r <- mapConcurrently makeAction cas-    let actions = filter wasRequested . foldMap unL $ rights r-    res <- send caps actions-    return $ Right res-  where-    wasRequested :: CAResult -> Bool-    wasRequested (CACommand _) = True-    wasRequested (CACodeAction ca)-      | Nothing <- only context = True-      | Just (List allowed) <- only context-      , Just caKind <- ca ^. kind = caKind `elem` allowed-      | otherwise = False--    wrapCodeAction :: C.ClientCapabilities -> CAResult -> IO (Maybe CAResult)-    wrapCodeAction _ (CACommand cmd) = return $ Just (CACommand cmd)-    wrapCodeAction caps (CACodeAction action) = do--      let (C.ClientCapabilities _ textDocCaps _ _) = caps-      let literalSupport = textDocCaps >>= C._codeAction >>= C._codeActionLiteralSupport--      case literalSupport of-        Nothing -> do-            let cmdParams = [J.toJSON (FallbackCodeActionParams (action ^. edit) (action ^. command))]-            cmd <- mkLspCommand "hls" "fallbackCodeAction" (action ^. title) (Just cmdParams)-            return $ Just (CACommand cmd)-        Just _ -> return $ Just (CACodeAction action)--    send :: C.ClientCapabilities -> [CAResult] -> IO (List CAResult)-    send caps codeActions = List . catMaybes <$> mapM (wrapCodeAction caps) codeActions--data FallbackCodeActionParams =-  FallbackCodeActionParams-    { fallbackWorkspaceEdit :: Maybe WorkspaceEdit-    , fallbackCommand       :: Maybe Command-    }-  deriving (Generic, J.ToJSON, J.FromJSON)---- -------------------------------------------------------------codeLensPlugins :: [(PluginId, CodeLensProvider IdeState)] -> Plugin Config-codeLensPlugins cas = Plugin codeLensRules (codeLensHandlers cas)--codeLensRules :: Rules ()-codeLensRules = mempty--codeLensHandlers :: [(PluginId, CodeLensProvider IdeState)] -> PartialHandlers Config-codeLensHandlers cas = PartialHandlers $ \WithMessage{..} x -> return x-    { LSP.codeLensHandler-        = withResponse RspCodeLens (makeCodeLens cas)-    }--makeCodeLens :: [(PluginId, CodeLensProvider IdeState)]-      -> LSP.LspFuncs Config-      -> IdeState-      -> CodeLensParams-      -> IO (Either ResponseError (List CodeLens))-makeCodeLens cas lf ideState params = do-    logDebug (ideLogger ideState) "Plugin.makeCodeLens (ideLogger)" -- AZ-    let-      makeLens (pid, provider) = do-          pluginConfig <- getPluginConfig lf pid-          r <- if pluginEnabled pluginConfig plcCodeLensOn-                 then otTracedProvider pid "codeLens" $ provider lf ideState pid params-                 else return $ Right (List [])-          return (pid, r)-      breakdown :: [(PluginId, Either ResponseError a)] -> ([(PluginId, ResponseError)], [(PluginId, a)])-      breakdown ls = (concatMap doOneLeft ls, concatMap doOneRight ls)-        where-          doOneLeft (pid, Left err) = [(pid,err)]-          doOneLeft (_, Right _) = []--          doOneRight (pid, Right a) = [(pid,a)]-          doOneRight (_, Left _) = []--    r <- mapConcurrently makeLens cas-    case breakdown r of-        ([],[]) -> return $ Right $ List []-        (es,[]) -> return $ Left $ ResponseError InternalError (T.pack $ "codeLens failed:" ++ show es) Nothing-        (_,rs) -> return $ Right $ List (concatMap (\(_,List cs) -> cs) rs)---- -----------------------------------------------------------+-- ---------------------------------------------------------------------  executeCommandPlugins :: [(PluginId, [PluginCommand IdeState])] -> Plugin Config executeCommandPlugins ecs = Plugin mempty (executeCommandHandlers ecs) -executeCommandHandlers :: [(PluginId, [PluginCommand IdeState])] -> PartialHandlers Config-executeCommandHandlers ecs = PartialHandlers $ \WithMessage{..} x -> return x{-    LSP.executeCommandHandler = withResponseAndRequest RspExecuteCommand ReqApplyWorkspaceEdit (makeExecuteCommands ecs)-    }--makeExecuteCommands :: [(PluginId, [PluginCommand IdeState])] -> LSP.LspFuncs Config -> ExecuteCommandProvider IdeState-makeExecuteCommands ecs lf ide = wrapUnhandledExceptions $ do-  let-      pluginMap = Map.fromList ecs-      parseCmdId :: T.Text -> Maybe (PluginId, CommandId)-      parseCmdId x = case T.splitOn ":" x of-        [plugin, command] -> Just (PluginId plugin, CommandId command)-        [_, plugin, command] -> Just (PluginId plugin, CommandId command)-        _ -> Nothing--      execCmd :: ExecuteCommandParams -> IO (Either ResponseError J.Value, Maybe (ServerMethod, ApplyWorkspaceEditParams))-      execCmd (ExecuteCommandParams cmdId args _) = do-        -- The parameters to the HIE command are always the first element-        let cmdParams :: J.Value-            cmdParams = case args of-             Just (J.List (x:_)) -> x-             _ -> J.Null--        case parseCmdId cmdId of-          -- Shortcut for immediately applying a applyWorkspaceEdit as a fallback for v3.8 code actions-          Just ("hls", "fallbackCodeAction") ->-            case J.fromJSON cmdParams of-              J.Success (FallbackCodeActionParams mEdit mCmd) -> do--                -- Send off the workspace request if it has one-                forM_ mEdit $ \edit -> do-                  let eParams = J.ApplyWorkspaceEditParams edit-                  reqId <- LSP.getNextReqId lf-                  LSP.sendFunc lf $ ReqApplyWorkspaceEdit $ RequestMessage "2.0" reqId WorkspaceApplyEdit eParams--                case mCmd of-                  -- If we have a command, continue to execute it-                  Just (J.Command _ innerCmdId innerArgs)-                      -> execCmd (ExecuteCommandParams innerCmdId innerArgs Nothing)-                  Nothing -> return (Right J.Null, Nothing)--              J.Error _str -> return (Right J.Null, Nothing)--          -- Just an ordinary HIE command-          Just (plugin, cmd) -> runPluginCommand pluginMap lf ide plugin cmd cmdParams+executeCommandHandlers :: [(PluginId, [PluginCommand IdeState])] -> LSP.Handlers (ServerM Config)+executeCommandHandlers ecs = requestHandler SWorkspaceExecuteCommand execCmd+  where+    pluginMap = Map.fromList ecs -          -- Couldn't parse the command identifier-          _ -> return (Left $ ResponseError InvalidParams "Invalid command identifier" Nothing, Nothing)+    parseCmdId :: T.Text -> Maybe (PluginId, CommandId)+    parseCmdId x = case T.splitOn ":" x of+      [plugin, command] -> Just (PluginId plugin, CommandId command)+      [_, plugin, command] -> Just (PluginId plugin, CommandId command)+      _ -> Nothing -  execCmd+    -- The parameters to the HLS command are always the first element +    execCmd ide (ExecuteCommandParams _ cmdId args) = do+      let cmdParams :: J.Value+          cmdParams = case args of+            Just (J.List (x:_)) -> x+            _ -> J.Null+      case parseCmdId cmdId of+        -- Shortcut for immediately applying a applyWorkspaceEdit as a fallback for v3.8 code actions+        Just ("hls", "fallbackCodeAction") ->+          case J.fromJSON cmdParams of+            J.Success (FallbackCodeActionParams mEdit mCmd) -> do --- ------------------------------------------------------------wrapUnhandledExceptions ::-    (a -> IO (Either ResponseError J.Value, Maybe b)) ->-       a -> IO (Either ResponseError J.Value, Maybe b)-wrapUnhandledExceptions action input =-    catch (action input) $ \(e::SomeException) -> do-        let resp = ResponseError InternalError (T.pack $ show e) Nothing-        return (Left resp, Nothing)+              -- Send off the workspace request if it has one+              forM_ mEdit $ \edit ->+                LSP.sendRequest SWorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing edit) (\_ -> pure ()) +              case mCmd of+                -- If we have a command, continue to execute it+                Just (J.Command _ innerCmdId innerArgs)+                    -> execCmd ide (ExecuteCommandParams Nothing innerCmdId innerArgs)+                Nothing -> return $ Right J.Null --- | Runs a plugin command given a PluginId, CommandId and--- arguments in the form of a JSON object.-runPluginCommand :: Map.Map PluginId [PluginCommand IdeState]-                 -> LSP.LspFuncs Config-                 -> IdeState-                 -> PluginId-                 -> CommandId-                 -> J.Value-                 -> IO (Either ResponseError J.Value,-                        Maybe (ServerMethod, ApplyWorkspaceEditParams))-runPluginCommand m lf ide  p@(PluginId p') com@(CommandId com') arg =-  case Map.lookup p m of-    Nothing -> return-      (Left $ ResponseError InvalidRequest ("Plugin " <> p' <> " doesn't exist") Nothing, Nothing)-    Just xs -> case List.find ((com ==) . commandId) xs of-      Nothing -> return (Left $-        ResponseError InvalidRequest ("Command " <> com' <> " isn't defined for plugin " <> p'-                                      <> ". Legal commands are: " <> T.pack(show $ map commandId xs)) Nothing, Nothing)-      Just (PluginCommand _ _ f) -> case J.fromJSON arg of-        J.Error err -> return (Left $-          ResponseError InvalidParams ("error while parsing args for " <> com' <> " in plugin " <> p'-                                       <> ": " <> T.pack err-                                       <> "\narg = " <> T.pack (show arg)) Nothing, Nothing)-        J.Success a -> f lf ide a+            J.Error _str -> return $ Right J.Null --- -----------------------------------------------------------+        -- Just an ordinary HIE command+        Just (plugin, cmd) -> runPluginCommand ide plugin cmd cmdParams -mkLspCommand :: PluginId -> CommandId -> T.Text -> Maybe [J.Value] -> IO Command-mkLspCommand plid cn title args' = do-  pid <- T.pack . show <$> getProcessID-  let cmdId = mkLspCmdId pid plid cn-  let args = List <$> args'-  return $ Command title cmdId args+        -- Couldn't parse the command identifier+        _ -> return $ Left $ ResponseError InvalidParams "Invalid command identifier" Nothing -mkLspCmdId :: T.Text -> PluginId -> CommandId -> T.Text-mkLspCmdId pid (PluginId plid) (CommandId cid)-  = pid <> ":" <> plid <> ":" <> cid+    runPluginCommand ide p@(PluginId p') com@(CommandId com') arg =+      case Map.lookup p pluginMap  of+        Nothing -> return+          (Left $ ResponseError InvalidRequest ("Plugin " <> p' <> " doesn't exist") Nothing)+        Just xs -> case List.find ((com ==) . commandId) xs of+          Nothing -> return $ Left $+            ResponseError InvalidRequest ("Command " <> com' <> " isn't defined for plugin " <> p'+                                          <> ". Legal commands are: " <> T.pack(show $ map commandId xs)) Nothing+          Just (PluginCommand _ _ f) -> case J.fromJSON arg of+            J.Error err -> return $ Left $+              ResponseError InvalidParams ("error while parsing args for " <> com' <> " in plugin " <> p'+                                           <> ": " <> T.pack err+                                           <> "\narg = " <> T.pack (show arg)) Nothing+            J.Success a -> f ide a  -- --------------------------------------------------------------------- -hoverPlugins :: [(PluginId, HoverProvider IdeState)] -> Plugin Config-hoverPlugins hs = Plugin hoverRules (hoverHandlers hs)--hoverRules :: Rules ()-hoverRules = mempty--hoverHandlers :: [(PluginId, HoverProvider IdeState)] -> PartialHandlers Config-hoverHandlers hps = PartialHandlers $ \WithMessage{..} x ->-  return x{LSP.hoverHandler = withResponse RspHover (makeHover hps)}--makeHover :: [(PluginId, HoverProvider IdeState)]-      -> LSP.LspFuncs Config -> IdeState-      -> TextDocumentPositionParams-      -> IO (Either ResponseError (Maybe Hover))-makeHover hps lf ideState params-  = do-      let-        makeHover(pid,p) = do-          pluginConfig <- getPluginConfig lf pid-          if pluginEnabled pluginConfig plcHoverOn-             then otTracedProvider pid "hover" $ p ideState params-             else return $ Right Nothing-      mhs <- mapConcurrently makeHover hps-      -- TODO: We should support ServerCapabilities and declare that-      -- we don't support hover requests during initialization if we-      -- don't have any hover providers-      -- TODO: maybe only have provider give MarkedString and-      -- work out range here?-      let hs = catMaybes (rights mhs)-          r = listToMaybe $ mapMaybe (^. range) hs-          h = case foldMap (^. contents) hs of-            HoverContentsMS (List []) -> Nothing-            hh                        -> Just $ Hover hh r-      return $ Right h---- ------------------------------------------------------------------------ -----------------------------------------------------------------------symbolsPlugins :: [(PluginId, SymbolsProvider IdeState)] -> Plugin Config-symbolsPlugins hs = Plugin symbolsRules (symbolsHandlers hs)--symbolsRules :: Rules ()-symbolsRules = mempty--symbolsHandlers :: [(PluginId, SymbolsProvider IdeState)] -> PartialHandlers Config-symbolsHandlers hps = PartialHandlers $ \WithMessage{..} x ->-  return x {LSP.documentSymbolHandler = withResponse RspDocumentSymbols (makeSymbols hps)}--makeSymbols :: [(PluginId, SymbolsProvider IdeState)]-      -> LSP.LspFuncs Config-      -> IdeState-      -> DocumentSymbolParams-      -> IO (Either ResponseError DSResult)-makeSymbols sps lf ideState params-  = do-      let uri' = params ^. textDocument . uri-          (C.ClientCapabilities _ tdc _ _) = LSP.clientCapabilities lf-          supportsHierarchy = Just True == (tdc >>= C._documentSymbol >>= C._hierarchicalDocumentSymbolSupport)-          convertSymbols :: [DocumentSymbol] -> DSResult-          convertSymbols symbs-            | supportsHierarchy = DSDocumentSymbols $ List symbs-            | otherwise = DSSymbolInformation (List $ concatMap (go Nothing) symbs)-            where-                go :: Maybe T.Text -> DocumentSymbol -> [SymbolInformation]-                go parent ds =-                  let children' :: [SymbolInformation]-                      children' = concatMap (go (Just name')) (fromMaybe mempty (ds ^. children))-                      loc = Location uri' (ds ^. range)-                      name' = ds ^. name-                      si = SymbolInformation name' (ds ^. kind) (ds ^. deprecated) loc parent-                  in [si] <> children'--          makeSymbols (pid,p) = do-            pluginConfig <- getPluginConfig lf pid-            if pluginEnabled pluginConfig plcSymbolsOn-              then otTracedProvider pid "symbols" $ p lf ideState params-              else return $ Right []-      mhs <- mapConcurrently makeSymbols sps-      case rights mhs of-          [] -> return $ Left $ responseError $ T.pack $ show $ lefts mhs-          hs -> return $ Right $ convertSymbols $ concat hs----- ------------------------------------------------------------------------ -----------------------------------------------------------------------renamePlugins :: [(PluginId, RenameProvider IdeState)] -> Plugin Config-renamePlugins providers = Plugin rules handlers+extensiblePlugins :: [(PluginId, PluginHandlers IdeState)] -> Plugin Config+extensiblePlugins xs = Plugin mempty handlers   where-    rules = mempty-    handlers = PartialHandlers $ \WithMessage{..} x -> return x-      { LSP.renameHandler = withResponse RspRename (renameWith providers)}--renameWith ::-  [(PluginId, RenameProvider IdeState)] ->-  LSP.LspFuncs Config ->-  IdeState ->-  RenameParams ->-  IO (Either ResponseError WorkspaceEdit)-renameWith providers lspFuncs state params = do-    let-        makeAction (pid,p) = do-          pluginConfig <- getPluginConfig lspFuncs pid-          if pluginEnabled pluginConfig plcRenameOn-             then otTracedProvider pid "rename" $ p lspFuncs state params-             else return $ Right $ WorkspaceEdit Nothing Nothing-    -- TODO:AZ: we need to consider the right way to combine possible renamers-    results <- mapConcurrently makeAction providers-    case partitionEithers results of-        (errors, []) -> return $ Left $ responseError $ T.pack $ show errors-        (_, edits) -> return $ Right $ mconcat edits---- ------------------------------------------------------------------------ -----------------------------------------------------------------------formatterPlugins :: [(PluginId, FormattingProvider IdeState IO)] -> Plugin Config-formatterPlugins providers-    = Plugin formatterRules-             (formatterHandlers (Map.fromList (("none",noneProvider):providers)))--formatterRules :: Rules ()-formatterRules = mempty--formatterHandlers :: Map.Map PluginId (FormattingProvider IdeState IO) -> PartialHandlers Config-formatterHandlers providers = PartialHandlers $ \WithMessage{..} x -> return x-    { LSP.documentFormattingHandler-        = withResponse RspDocumentFormatting (formatting providers)-    , LSP.documentRangeFormattingHandler-        = withResponse RspDocumentRangeFormatting (rangeFormatting providers)-    }---- ------------------------------------------------------------------------ -----------------------------------------------------------------------completionsPlugins :: [(PluginId, CompletionProvider IdeState)] -> Plugin Config-completionsPlugins cs = Plugin completionsRules (completionsHandlers cs)--completionsRules :: Rules ()-completionsRules = mempty--completionsHandlers :: [(PluginId, CompletionProvider IdeState)] -> PartialHandlers Config-completionsHandlers cps = PartialHandlers $ \WithMessage{..} x ->-  return x {LSP.completionHandler = withResponse RspCompletion (makeCompletions cps)}--makeCompletions :: [(PluginId, CompletionProvider IdeState)]-      -> LSP.LspFuncs Config-      -> IdeState-      -> CompletionParams-      -> IO (Either ResponseError CompletionResponseResult)-makeCompletions sps lf ideState params@(CompletionParams (TextDocumentIdentifier doc) pos _context _mt)-  = do-      mprefix <- getPrefixAtPos lf doc pos-      maxCompletions <- maxCompletions <$> getClientConfig lf--      let-          combine :: [CompletionResponseResult] -> CompletionResponseResult-          combine cs = go True mempty cs--          go !comp acc [] =-            CompletionList (CompletionListType comp (List $ DList.toList acc))-          go comp acc (Completions (List ls) : rest) =-            go comp (acc <> DList.fromList ls) rest-          go comp acc (CompletionList (CompletionListType comp' (List ls)) : rest) =-            go (comp && comp') (acc <> DList.fromList ls) rest+    IdeHandlers handlers' = foldMap bakePluginId xs+    bakePluginId :: (PluginId, PluginHandlers IdeState) -> IdeHandlers+    bakePluginId (pid,PluginHandlers hs) = IdeHandlers $ DMap.map+      (\(PluginHandler f) -> IdeHandler [(pid,f pid)])+      hs+    handlers = mconcat $ do+      (IdeMethod m :=> IdeHandler fs') <- DMap.assocs handlers'+      pure $ requestHandler m $ \ide params -> do+        config <- getClientConfig+        let fs = filter (\(pid,_) -> pluginEnabled m pid config) fs'+        case nonEmpty fs of+          Nothing -> pure $ Left $ ResponseError InvalidRequest+            ("No plugin enabled for " <> T.pack (show m) <> ", available: " <> T.pack (show $ map fst fs))+            Nothing+          Just fs -> do+            let msg e pid = "Exception in plugin " <> T.pack (show pid) <> "while processing " <> T.pack (show m) <> ": " <> T.pack (show e)+            es <- runConcurrently msg (show m) fs ide params+            let (errs,succs) = partitionEithers $ toList es+            case nonEmpty succs of+              Nothing -> pure $ Left $ combineErrors errs+              Just xs -> do+                caps <- LSP.getClientCapabilities+                pure $ Right $ combineResponses m config caps params xs -          makeAction ::-            (PluginId, CompletionProvider IdeState) ->-            IO (Either ResponseError CompletionResponseResult)-          makeAction (pid, p) = do-            pluginConfig <- getPluginConfig lf pid-            if pluginEnabled pluginConfig plcCompletionOn-               then otTracedProvider pid "completions" $ p lf ideState params-               else return $ Right $ Completions $ List []+runConcurrently+  :: MonadUnliftIO m+  => (SomeException -> PluginId -> T.Text)+  -> String -- ^ label+  -> NonEmpty (PluginId, a -> b -> m (NonEmpty (Either ResponseError d)))+  -> a+  -> b+  -> m (NonEmpty (Either ResponseError d))+runConcurrently msg method fs a b = fmap join $ forConcurrently fs $ \(pid,f) -> otTracedProvider pid (fromString method) $ do+  f a b+    `catchAny` (\e -> pure $ pure $ Left $ ResponseError InternalError (msg e pid) Nothing) -      case mprefix of-          Nothing -> return $ Right $ Completions $ List []-          Just _prefix -> do-            mhs <- mapConcurrently makeAction sps-            case rights mhs of-                [] -> return $ Left $ responseError $ T.pack $ show $ lefts mhs-                hs -> return $ Right $ snd $ consumeCompletionResponse maxCompletions $ combine hs+combineErrors :: [ResponseError] -> ResponseError+combineErrors [x] = x+combineErrors xs = ResponseError InternalError (T.pack (show xs)) Nothing --- | Crops a completion response. Returns the final number of completions and the cropped response-consumeCompletionResponse :: Int -> CompletionResponseResult -> (Int, CompletionResponseResult)-consumeCompletionResponse limit it@(CompletionList (CompletionListType _ (List xx))) =-  case splitAt limit xx of-    -- consumed all the items, return the result as is-    (_, []) -> (limit - length xx, it)-    -- need to crop the response, set the 'isIncomplete' flag-    (xx', _) -> (0, CompletionList (CompletionListType isIncompleteResponse (List xx')))-consumeCompletionResponse n (Completions (List xx)) =-  consumeCompletionResponse n (CompletionList (CompletionListType isCompleteResponse (List xx)))+-- | Combine the 'PluginHandler' for all plugins+newtype IdeHandler (m :: J.Method FromClient Request)+  = IdeHandler [(PluginId,IdeState -> MessageParams m -> LSP.LspM Config (NonEmpty (Either ResponseError (ResponseResult m))))] --- boolean disambiguators-isCompleteResponse, isIncompleteResponse :: Bool-isIncompleteResponse = True-isCompleteResponse = False+-- | Combine the 'PluginHandlers' for all plugins+newtype IdeHandlers = IdeHandlers (DMap IdeMethod IdeHandler) -getPrefixAtPos :: LSP.LspFuncs Config -> Uri -> Position -> IO (Maybe VFS.PosPrefixInfo)-getPrefixAtPos lf uri pos = do-  mvf <-  LSP.getVirtualFileFunc lf (J.toNormalizedUri uri)-  case mvf of-    Just vf -> VFS.getCompletionPrefix pos vf-    Nothing -> return Nothing+instance Semigroup IdeHandlers where+  (IdeHandlers a) <> (IdeHandlers b) = IdeHandlers $ DMap.unionWithKey go a b+    where+      go _ (IdeHandler a) (IdeHandler b) = IdeHandler (a ++ b)+instance Monoid IdeHandlers where+  mempty = IdeHandlers mempty
− src/Development/IDE/Plugin/HLS/Formatter.hs
@@ -1,70 +0,0 @@--module Development.IDE.Plugin.HLS.Formatter-  (-    formatting-  , rangeFormatting-  )-where--import qualified Data.Map  as Map-import qualified Data.Text as T-import           Development.IDE-import           Ide.PluginUtils-import           Ide.Types-import           Ide.Plugin.Config-import qualified Language.Haskell.LSP.Core as LSP-import           Language.Haskell.LSP.Types-import           Text.Regex.TDFA.Text()---- -----------------------------------------------------------------------formatting :: Map.Map PluginId (FormattingProvider IdeState IO)-           -> LSP.LspFuncs Config -> IdeState -> DocumentFormattingParams-           -> IO (Either ResponseError (List TextEdit))-formatting providers lf ideState-    (DocumentFormattingParams (TextDocumentIdentifier uri) params _mprogress)-  = doFormatting lf providers ideState FormatText uri params---- -----------------------------------------------------------------------rangeFormatting :: Map.Map PluginId (FormattingProvider IdeState IO)-                -> LSP.LspFuncs Config -> IdeState -> DocumentRangeFormattingParams-                -> IO (Either ResponseError (List TextEdit))-rangeFormatting providers lf ideState-    (DocumentRangeFormattingParams (TextDocumentIdentifier uri) range params _mprogress)-  = doFormatting lf providers ideState (FormatRange range) uri params---- -----------------------------------------------------------------------doFormatting :: LSP.LspFuncs Config -> Map.Map PluginId (FormattingProvider IdeState IO)-             -> IdeState -> FormattingType -> Uri -> FormattingOptions-             -> IO (Either ResponseError (List TextEdit))-doFormatting lf providers ideState ft uri params = do-  mc <- LSP.config lf-  let mf = maybe "none" formattingProvider mc-  case Map.lookup (PluginId mf) providers of-      Just provider ->-        case uriToFilePath uri of-          Just (toNormalizedFilePath -> fp) -> do-            (_, mb_contents) <- runAction "Formatter" ideState $ getFileContents fp-            case mb_contents of-              Just contents -> do-                  logDebug (ideLogger ideState) $ T.pack $-                      "Formatter.doFormatting: contents=" ++ show contents -- AZ-                  provider lf ideState ft contents fp params-              Nothing -> return $ Left $ responseError $ T.pack $ "Formatter plugin: could not get file contents for " ++ show uri-          Nothing -> return $ Left $ responseError $ T.pack $ "Formatter plugin: uriToFilePath failed for: " ++ show uri-      Nothing -> return $ Left $ responseError $ mconcat-        [ "Formatter plugin: no formatter found for:["-        , mf-        , "]"-        , if mf == "brittany"-          then T.unlines-            [ "\nThe haskell-language-server must be compiled with the agpl flag to provide Brittany."-            , "Stack users add 'agpl: true' in the flags section of the 'stack.yaml' file."-            , "The 'haskell-language-server.cabal' file already has this flag enabled by default."-            , "For more information see: https://github.com/haskell/haskell-language-server/issues/269"-            ]-          else ""-        ]-
src/Development/IDE/Plugin/HLS/GhcIde.hs view
@@ -9,13 +9,14 @@ import Development.IDE import Development.IDE.LSP.HoverDefinition import Development.IDE.LSP.Outline-import Ide.PluginUtils import Ide.Types-import Language.Haskell.LSP.Types+import Language.LSP.Types+import Language.LSP.Server (LspM) import Text.Regex.TDFA.Text() import qualified Development.IDE.Plugin.CodeAction as CodeAction import qualified Development.IDE.Plugin.Completions as Completions import qualified Development.IDE.Plugin.TypeLenses as TypeLenses+import Control.Monad.IO.Class  descriptors :: [PluginDescriptor IdeState] descriptors =@@ -29,25 +30,19 @@  descriptor :: PluginId -> PluginDescriptor IdeState descriptor plId = (defaultPluginDescriptor plId)-  { pluginHoverProvider      = Just hover'-  , pluginSymbolsProvider    = Just symbolsProvider+  { pluginHandlers = mkPluginHandler STextDocumentHover hover'+                  <> mkPluginHandler STextDocumentDocumentSymbol symbolsProvider   }  -- --------------------------------------------------------------------- -hover' :: HoverProvider IdeState-hover' ideState params = do-    logDebug (ideLogger ideState) "GhcIde.hover entered (ideLogger)" -- AZ-    hover ideState params+hover' :: IdeState -> PluginId -> HoverParams  -> LspM c (Either ResponseError (Maybe Hover))+hover' ideState _ HoverParams{..} = do+    liftIO $ logDebug (ideLogger ideState) "GhcIde.hover entered (ideLogger)" -- AZ+    hover ideState TextDocumentPositionParams{..}  -- ----------------------------------------------------------------------symbolsProvider :: SymbolsProvider IdeState-symbolsProvider ls ide params = do-    ds <- moduleOutline ls ide params-    case ds of-        Right (DSDocumentSymbols (List ls)) -> return $ Right ls-        Right (DSSymbolInformation (List _si)) ->-            return $ Left $ responseError "GhcIde.symbolsProvider: DSSymbolInformation deprecated"-        Left err -> return $ Left err+symbolsProvider :: IdeState -> PluginId -> DocumentSymbolParams -> LspM c (Either ResponseError (List DocumentSymbol |? List SymbolInformation))+symbolsProvider ide _ params = moduleOutline ide params  -- ---------------------------------------------------------------------
src/Development/IDE/Plugin/Test.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE GADTs #-} {-# LANGUAGE DerivingStrategies #-} -- | A plugin that adds custom messages for use in tests module Development.IDE.Plugin.Test@@ -10,6 +11,7 @@   ) where  import Control.Monad.STM+import Control.Monad.IO.Class import Data.Aeson import Data.Aeson.Types import Data.CaseInsensitive (CI, original)@@ -17,14 +19,12 @@ import Development.IDE.Core.Shake import Development.IDE.GHC.Compat import Development.IDE.Types.HscEnvEq (HscEnvEq(hscEnv))-import Development.IDE.LSP.Server import Development.IDE.Plugin+import Development.IDE.LSP.Server 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 Language.LSP.Types import System.Time.Extra import Development.IDE.Core.RuleTypes import Control.Monad@@ -36,7 +36,7 @@ import Development.IDE.Types.Location (fromUri) import Control.Concurrent (threadDelay) import Ide.Types-import qualified Language.Haskell.LSP.Core as LSP+import qualified Language.LSP.Server as LSP  data TestRequest     = BlockSeconds Seconds           -- ^ :: Null@@ -53,42 +53,39 @@ plugin :: Plugin c plugin = Plugin {     pluginRules = return (),-    pluginHandler = PartialHandlers $ \WithMessage{..} x -> return x {-        customRequestHandler = withResponse RspCustomServer requestHandler'-    }+    pluginHandlers = requestHandler (SCustomMethod "test") testRequestHandler' }   where-      requestHandler' lsp ide req+      testRequestHandler' ide req         | Just customReq <- parseMaybe parseJSON req-        = requestHandler lsp ide customReq+        = testRequestHandler ide customReq         | otherwise         = return $ Left         $ ResponseError InvalidRequest "Cannot parse request" Nothing -requestHandler :: LspFuncs c-                -> IdeState++testRequestHandler ::  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+                -> LSP.LspM c (Either ResponseError Value)+testRequestHandler _ (BlockSeconds secs) = do+    LSP.sendNotification (SCustomMethod "ghcide/blocking/request") $+      toJSON secs+    liftIO $ sleep secs     return (Right Null)-requestHandler _ s (GetInterfaceFilesDir fp) = do+testRequestHandler s (GetInterfaceFilesDir fp) = liftIO $ 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+testRequestHandler s GetShakeSessionQueueCount = liftIO $ do     n <- atomically $ countQueue $ actionQueue $ shakeExtras s     return $ Right (toJSON n)-requestHandler _ s WaitForShakeQueue = do+testRequestHandler s WaitForShakeQueue = liftIO $ do     atomically $ do         n <- countQueue $ actionQueue $ shakeExtras s         when (n>0) retry     return $ Right Null-requestHandler _ s (WaitForIdeRule k file) = do+testRequestHandler s (WaitForIdeRule k file) = liftIO $ do     let nfp = fromUri $ toNormalizedUri file     success <- runAction ("WaitForIdeRule " <> k <> " " <> show file) s $ parseAction (fromString k) nfp     let res = WaitForIdeRuleResult <$> success@@ -120,9 +117,7 @@ }  blockCommandHandler :: CommandFunction state ExecuteCommandParams-blockCommandHandler lsp _ideState _params-    = do-        LSP.sendFunc lsp $ NotCustomServer $-            NotificationMessage "2.0" (CustomServerMethod "ghcide/blocking/command") Null-        threadDelay maxBound-        return (Right Null, Nothing)+blockCommandHandler _ideState _params = do+  LSP.sendNotification (SCustomMethod "ghcide/blocking/command") Null+  liftIO $ threadDelay maxBound+  return (Right Null)
src/Development/IDE/Plugin/TypeLenses.hs view
@@ -6,6 +6,7 @@   ) where +import Control.Monad.IO.Class import Data.Aeson.Types (Value (..), toJSON) import qualified Data.HashMap.Strict as Map import qualified Data.Text as T@@ -24,22 +25,23 @@   ( CommandFunction,     CommandId (CommandId),     PluginCommand (PluginCommand),-    PluginDescriptor (pluginCodeLensProvider, pluginCommands),+    PluginDescriptor(..),     PluginId,     defaultPluginDescriptor,+    mkPluginHandler   )-import qualified Language.Haskell.LSP.Core as LSP-import Language.Haskell.LSP.Types+import qualified Language.LSP.Server as LSP+import Language.LSP.Types   ( ApplyWorkspaceEditParams (ApplyWorkspaceEditParams),     CodeLens (CodeLens),     CodeLensParams (CodeLensParams, _textDocument),     Diagnostic (..),     List (..),     ResponseError,-    ServerMethod (WorkspaceApplyEdit),     TextDocumentIdentifier (TextDocumentIdentifier),     TextEdit (TextEdit),     WorkspaceEdit (WorkspaceEdit),+    SMethod(..)   ) import Text.Regex.TDFA ((=~)) @@ -49,19 +51,18 @@ descriptor :: PluginId -> PluginDescriptor IdeState descriptor plId =   (defaultPluginDescriptor plId)-    { pluginCodeLensProvider = Just codeLensProvider,+    { pluginHandlers = mkPluginHandler STextDocumentCodeLens codeLensProvider,       pluginCommands = [PluginCommand (CommandId typeLensCommandId) "adds a signature" commandHandler]     }  codeLensProvider ::-  LSP.LspFuncs c ->   IdeState ->   PluginId ->   CodeLensParams ->-  IO (Either ResponseError (List CodeLens))-codeLensProvider _lsp ideState pId CodeLensParams {_textDocument = TextDocumentIdentifier uri} = do+  LSP.LspM c (Either ResponseError (List CodeLens))+codeLensProvider ideState pId CodeLensParams {_textDocument = TextDocumentIdentifier uri} = do   fmap (Right . List) $ case uriToFilePath' uri of-    Just (toNormalizedFilePath' -> filePath) -> do+    Just (toNormalizedFilePath' -> filePath) -> liftIO $ do       _ <- runAction "codeLens" ideState (use TypeCheck filePath)       diag <- getDiagnostics ideState       hDiag <- getHiddenDiagnostics ideState@@ -76,12 +77,13 @@  generateLens :: PluginId -> Range -> T.Text -> WorkspaceEdit -> IO CodeLens generateLens pId _range title edit = do-  cId <- mkLspCommand pId (CommandId typeLensCommandId) title (Just [toJSON edit])+  let cId = mkLspCommand pId (CommandId typeLensCommandId) title (Just [toJSON edit])   return $ CodeLens _range (Just cId) Nothing  commandHandler :: CommandFunction IdeState WorkspaceEdit-commandHandler _lsp _ideState wedit =-    return (Right Null, Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams wedit))+commandHandler _ideState wedit = do+  _ <- LSP.sendRequest SWorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing wedit) (\_ -> pure ())+  return $ Right Null  suggestSignature :: Bool -> Diagnostic -> [(T.Text, [TextEdit])] suggestSignature isQuickFix Diagnostic {_range = _range@Range {..}, ..}
src/Development/IDE/Spans/AtPoint.hs view
@@ -22,7 +22,7 @@ import Development.IDE.GHC.Error import Development.IDE.GHC.Orphans() import Development.IDE.Types.Location-import Language.Haskell.LSP.Types+import           Language.LSP.Types  -- compiler and infrastructure import Development.IDE.GHC.Compat@@ -318,7 +318,15 @@       mod <- MaybeT $ return $ nameModule_maybe name       erow <- liftIO $ findDef hiedb (nameOccName name) (Just $ moduleName mod) (Just $ moduleUnitId mod)       case erow of-        [] -> MaybeT $ pure Nothing+        [] -> do+          -- If the lookup failed, try again without specifying a unit-id.+          -- This is a hack to make find definition work better with ghcide's nascent multi-component support,+          -- where names from a component that has been indexed in a previous session but not loaded in this+          -- session may end up with different unit ids+          erow <- liftIO $ findDef hiedb (nameOccName name) (Just $ moduleName mod) Nothing+          case erow of+            [] -> MaybeT $ pure Nothing+            xs -> lift $ mapMaybeM (runMaybeT . defRowToLocation lookupModule) xs         xs -> lift $ mapMaybeM (runMaybeT . defRowToLocation lookupModule) xs  defRowToLocation :: Monad m => LookupModule m -> Res DefRow -> MaybeT m Location
src/Development/IDE/Spans/Documentation.hs view
@@ -35,7 +35,7 @@ import           GhcMonad import           Packages import           Name-import           Language.Haskell.LSP.Types (getUri, filePathToUri)+import           Language.LSP.Types (getUri, filePathToUri) import           TcRnTypes import           ExtractDocs import           NameEnv@@ -43,11 +43,10 @@  mkDocMap   :: HscEnv-  -> [ParsedModule]   -> RefMap a   -> TcGblEnv   -> IO DocAndKindMap-mkDocMap env sources rm this_mod =+mkDocMap env rm this_mod =   do let (_ , DeclDocMap this_docs, _) = extractDocs this_mod      d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names      k <- foldrM getType (tcg_type_env this_mod) names@@ -56,7 +55,7 @@     getDocs n map       | maybe True (mod ==) $ nameModule_maybe n = pure map -- we already have the docs in this_docs, or they do not exist       | otherwise = do-      doc <- getDocumentationTryGhc env mod sources n+      doc <- getDocumentationTryGhc env mod n       pure $ extendNameEnv map n doc     getType n map       | isTcOcc $ occName n = do@@ -71,23 +70,21 @@ lookupKind env mod =     fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod -getDocumentationTryGhc :: HscEnv -> Module -> [ParsedModule] -> Name -> IO SpanDoc-getDocumentationTryGhc env mod deps n = head <$> getDocumentationsTryGhc env mod deps [n]+getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc+getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n] -getDocumentationsTryGhc :: HscEnv -> Module -> [ParsedModule] -> [Name] -> IO [SpanDoc]--- Interfaces are only generated for GHC >= 8.6.--- In older versions, interface files do not embed Haddocks anyway-getDocumentationsTryGhc env mod sources names = do+getDocumentationsTryGhc :: HscEnv -> Module -> [Name] -> IO [SpanDoc]+getDocumentationsTryGhc env mod names = do   res <- catchSrcErrors (hsc_dflags env) "docs" $ getDocsBatch env mod names   case res of-      Left _ -> mapM mkSpanDocText names+      Left _ -> return []       Right res -> zipWithM unwrap res names   where     unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n     unwrap _ n = mkSpanDocText n      mkSpanDocText name =-      SpanDocText (getDocumentation sources name) <$> getUris name+      SpanDocText [] <$> getUris name      -- Get the uris to the documentation and source html pages if they exist     getUris name = do@@ -212,8 +209,8 @@     go pkgDocDir = map (mkDocPath pkgDocDir) mns     ui = moduleUnitId m     -- try to locate html file from most to least specific name e.g.-    --  first Language.Haskell.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html-    --  then Language.Haskell.LSP.Types.html and Language-Haskell-LSP-Types.html etc.+    --  first Language.LSP.Types.Uri.html and Language-Haskell-LSP-Types-Uri.html+    --  then Language.LSP.Types.html and Language-Haskell-LSP-Types.html etc.     mns = do       chunks <- (reverse . drop1 . inits . splitOn ".") $ (moduleNameString . moduleName) m       -- The file might use "." or "-" as separator
src/Development/IDE/Types/Diagnostics.hs view
@@ -20,12 +20,12 @@ import Data.Maybe as Maybe import qualified Data.Text as T import Data.Text.Prettyprint.Doc-import Language.Haskell.LSP.Types as LSP (DiagnosticSource,+import Language.LSP.Types as LSP (DiagnosticSource,     DiagnosticSeverity(..)   , Diagnostic(..)   , List(..)   )-import Language.Haskell.LSP.Diagnostics+import Language.LSP.Diagnostics import Data.Text.Prettyprint.Doc.Render.Text import qualified Data.Text.Prettyprint.Doc.Render.Terminal as Terminal import Data.Text.Prettyprint.Doc.Render.Terminal (Color(..), color)
src/Development/IDE/Types/HscEnvEq.hs view
@@ -6,6 +6,7 @@     newHscEnvEqWithImportPaths,     envImportPaths,     envPackageExports,+    envVisibleModuleNames,     deps ) where @@ -16,7 +17,7 @@ import Module (InstalledUnitId) import System.Directory (canonicalizePath) import Development.IDE.GHC.Compat-import GhcPlugins(HscEnv (hsc_dflags), PackageState (explicitPackages), InstalledPackageInfo (exposedModules), Module(..), packageConfigId)+import GhcPlugins(HscEnv (hsc_dflags), PackageState (explicitPackages), InstalledPackageInfo (exposedModules), Module(..), packageConfigId, listVisibleModuleNames) import System.FilePath import Development.IDE.GHC.Util (lookupPackageConfig) import Control.Monad.IO.Class@@ -24,8 +25,13 @@ import LoadIface (loadInterface) import qualified Maybes import OpenTelemetry.Eventlog (withSpan)-import System.IO.Unsafe (unsafePerformIO)-import Control.Monad.Extra (mapMaybeM)+import Control.Monad.Extra (mapMaybeM, join, eitherM)+import Control.Concurrent.Extra (newVar, modifyVar)+import Control.Concurrent.Async (Async, async, waitCatch)+import Control.Exception (throwIO, mask, evaluate)+import Development.IDE.GHC.Error (catchSrcErrors)+import Control.DeepSeq (force)+import Data.Either (fromRight)  -- | An 'HscEnv' with equality. Two values are considered equal --   if they are created with the same call to 'newHscEnvEq'.@@ -39,7 +45,12 @@     , envImportPaths :: Maybe [String]         -- ^ If Just, import dirs originally configured in this env         --   If Nothing, the env import dirs are unaltered-    , envPackageExports :: ExportsMap+    , envPackageExports :: IO ExportsMap+    , envVisibleModuleNames :: IO (Maybe [ModuleName])+        -- ^ 'listVisibleModuleNames' is a pure function,+        -- but it could panic due to a ghc bug: https://github.com/haskell/haskell-language-server/issues/1365+        -- So it's wrapped in IO here for error handling+        -- If Nothing, 'listVisibleModuleNames' panic     }  -- | Wrap an 'HscEnv' into an 'HscEnvEq'.@@ -56,13 +67,15 @@  newHscEnvEqWithImportPaths :: Maybe [String] -> HscEnv -> [(InstalledUnitId, DynFlags)] -> IO HscEnvEq newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do++    let dflags = hsc_dflags hscEnv+     envUnique <- newUnique -    let-    -- evaluate lazily, using unsafePerformIO for a pure API-      envPackageExports = unsafePerformIO $ withSpan "Package Exports" $ \_sp -> do+    -- it's very important to delay the package exports computation+    envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do         -- compute the package imports-        let pkgst   = pkgState (hsc_dflags hscEnv)+        let pkgst   = pkgState dflags             depends = explicitPackages pkgst             targets =                 [ (pkg, mn)@@ -81,6 +94,15 @@                     Maybes.Succeeded mi -> Just mi         modIfaces <- mapMaybeM doOne targets         return $ createExportsMap modIfaces++    -- similar to envPackageExports, evaluated lazily+    envVisibleModuleNames <- onceAsync $+      fromRight Nothing+        <$> catchSrcErrors+          dflags+          "listVisibleModuleNames"+          (evaluate . force . Just $ listVisibleModuleNames dflags)+     return HscEnvEq{..}  -- | Wrap an 'HscEnv' into an 'HscEnvEq'.@@ -107,8 +129,8 @@   a == b = envUnique a == envUnique b  instance NFData HscEnvEq where-  rnf (HscEnvEq a b c d _) =-      -- deliberately skip the package exports map+  rnf (HscEnvEq a b c d _ _) =+      -- deliberately skip the package exports map and visible module names       rnf (hashUnique a) `seq` b `seq` c `seq` rnf d  instance Hashable HscEnvEq where@@ -119,3 +141,19 @@ instance Binary HscEnvEq where   put _ = error "not really"   get = error "not really"++-- | Given an action, produce a wrapped action that runs at most once.+--   The action is run in an async so it won't be killed by async exceptions+--   If the function raises an exception, the same exception will be reraised each time.+onceAsync :: IO a -> IO (IO a)+onceAsync act = do+    var <- newVar OncePending+    let run as = eitherM throwIO pure (waitCatch as)+    pure $ mask $ \unmask -> join $ modifyVar var $ \v -> case v of+        OnceRunning x -> pure (v, unmask $ run x)+        OncePending -> do+            x <- async (unmask act)+            pure (OnceRunning x, unmask $ run x)++data Once a = OncePending | OnceRunning (Async a)+
src/Development/IDE/Types/Location.hs view
@@ -26,12 +26,12 @@     ) where  import Control.Applicative-import Language.Haskell.LSP.Types (Location(..), Range(..), Position(..))+import Language.LSP.Types (Location(..), Range(..), Position(..)) import Control.Monad import Data.Hashable (Hashable(hash)) import Data.String import FastString-import qualified Language.Haskell.LSP.Types as LSP+import qualified Language.LSP.Types as LSP import SrcLoc as GHC import Text.ParserCombinators.ReadP as ReadP import Data.Maybe (fromMaybe)
src/Development/IDE/Types/Options.hs view
@@ -17,12 +17,11 @@   , OptHaddockParse(..)   ,optShakeFiles) where -import Data.Default import Development.Shake import Development.IDE.Types.HscEnvEq (HscEnvEq) import           GHC hiding (parseModule, typecheckModule) import           GhcPlugins                     as GHC hiding (fst3, (<>))-import qualified Language.Haskell.LSP.Types.Capabilities as LSP+import qualified Language.LSP.Types.Capabilities as LSP import qualified Data.Text as T import Development.IDE.Types.Diagnostics import Control.DeepSeq (NFData(..))@@ -72,9 +71,9 @@     --   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 :: !Bool+  , optCheckProject :: IO Bool     -- ^ Whether to typecheck the entire project on load-  , optCheckParents :: CheckParents+  , optCheckParents :: IO CheckParents     -- ^ When to typecheck reverse dependencies of a file   , optHaddockParse :: OptHaddockParse     -- ^ Whether to return result of parsing module with Opt_Haddock.@@ -133,8 +132,8 @@     ,optKeywords = haskellKeywords     ,optDefer = IdeDefer True     ,optTesting = IdeTesting False-    ,optCheckProject = checkProject def-    ,optCheckParents = checkParents def+    ,optCheckProject = pure True+    ,optCheckParents = pure CheckOnSaveAndClose     ,optHaddockParse = HaddockParse     ,optCustomDynFlags = id     }
src/Development/IDE/Types/Shake.hs view
@@ -28,7 +28,7 @@ import Development.Shake (RuleResult, ShakeException (shakeExceptionInner)) import Development.Shake.Classes import GHC.Generics-import Language.Haskell.LSP.Types+import Language.LSP.Types import Development.IDE.Core.PositionMapping  data Value v
test/exe/Main.hs view
@@ -5,7 +5,12 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE ImplicitParams  #-} {-# LANGUAGE PatternSynonyms #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE PolyKinds #-} {-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wno-deprecations -Wno-unticked-promoted-constructors #-} #include "ghc-api-version.h"  module Main (main) where@@ -15,7 +20,7 @@ import qualified Control.Lens as Lens import Control.Monad import Control.Monad.IO.Class (MonadIO, liftIO)-import Data.Aeson (FromJSON, Value, toJSON,fromJSON)+import Data.Aeson (toJSON,fromJSON) import qualified Data.Aeson as A import qualified Data.Binary as Binary import Data.Default@@ -29,7 +34,6 @@ import Development.IDE.Core.Shake (Q(..)) import Development.IDE.GHC.Util import qualified Data.Text as T-import Data.Typeable import Development.IDE.Plugin.Completions.Types (extendImportCommandId) import Development.IDE.Plugin.TypeLenses (typeLensCommandId) import Development.IDE.Spans.Common@@ -50,12 +54,11 @@ import Development.Shake (getDirectoryFilesIO) import Ide.Plugin.Config import qualified Experiments as Bench-import Language.Haskell.LSP.Test-import Language.Haskell.LSP.Messages-import Language.Haskell.LSP.Types-import Language.Haskell.LSP.Types.Capabilities-import qualified Language.Haskell.LSP.Types.Lens as Lsp (diagnostics, params, message)-import Language.Haskell.LSP.VFS (applyChange)+import Language.LSP.Test+import Language.LSP.Types hiding (mkRange)+import Language.LSP.Types.Capabilities+import qualified Language.LSP.Types.Lens as Lsp (diagnostics, params, message)+import Language.LSP.VFS (applyChange) import Network.URI import System.Environment.Blank (unsetEnv, getEnv, setEnv) import System.FilePath@@ -66,7 +69,7 @@ import System.Process.Extra (readCreateProcessWithExitCode, CreateProcess(cwd), proc) import System.Info.Extra (isWindows) import Test.QuickCheck-import Test.QuickCheck.Instances ()+-- import Test.QuickCheck.Instances () import Test.Tasty import Test.Tasty.ExpectedFailure import Test.Tasty.Ingredients.Rerun@@ -76,21 +79,31 @@ import Development.IDE.Plugin.CodeAction (matchRegExMultipleImports) import Development.IDE.Plugin.Test (TestRequest (BlockSeconds, GetInterfaceFilesDir), WaitForIdeRuleResult (..), blockCommandId) import Control.Monad.Extra (whenJust)-import qualified Language.Haskell.LSP.Types.Lens as L+import qualified Language.LSP.Types.Lens as L import Control.Lens ((^.)) import Data.Functor import Data.Tuple.Extra +waitForProgressBegin :: Session ()+waitForProgressBegin = skipManyTill anyMessage $ satisfyMaybe $ \case+  FromServerMess SProgress (NotificationMessage _ _ (ProgressParams _ (Begin _))) -> Just ()+  _ -> Nothing++waitForProgressDone :: Session ()+waitForProgressDone = skipManyTill anyMessage $ satisfyMaybe $ \case+  FromServerMess SProgress (NotificationMessage _ _ (ProgressParams _ (End _))) -> Just ()+  _ -> Nothing+ main :: IO () main = do   -- We mess with env vars so run single-threaded.   defaultMainWithRerun $ testGroup "ghcide"     [ testSession "open close" $ do         doc <- createDoc "Testing.hs" "haskell" ""-        void (skipManyTill anyMessage message :: Session WorkDoneProgressCreateRequest)-        void (skipManyTill anyMessage message :: Session WorkDoneProgressBeginNotification)+        void (skipManyTill anyMessage $ message SWindowWorkDoneProgressCreate)+        waitForProgressBegin         closeDoc doc-        void (skipManyTill anyMessage message :: Session WorkDoneProgressEndNotification)+        waitForProgressDone     , initializeResponseTests     , completionTests     , cppTests@@ -130,50 +143,50 @@   -- response. Currently the server advertises almost no capabilities   -- at all, in some cases failing to announce capabilities that it   -- actually does provide! Hopefully this will change ...-  tests :: IO InitializeResponse -> TestTree+  tests :: IO (ResponseMessage Initialize) -> TestTree   tests getInitializeResponse =     testGroup "initialize response capabilities"     [ chk "   text doc sync"             _textDocumentSync  tds-    , chk "   hover"                         _hoverProvider (Just True)-    , chk "   completion"               _completionProvider (Just $ CompletionOptions (Just False) (Just ["."]) Nothing)-    , chk "NO signature help"        _signatureHelpProvider  Nothing-    , chk "   goto definition"          _definitionProvider (Just True)-    , chk "   goto type definition" _typeDefinitionProvider (Just $ GotoOptionsStatic True)+    , chk "   hover"                         _hoverProvider (Just $ InL True)+    , chk "   completion"               _completionProvider (Just $ CompletionOptions Nothing (Just ["."]) Nothing (Just False))+    , chk "NO signature help"        _signatureHelpProvider Nothing+    , chk "   goto definition"          _definitionProvider (Just $ InL True)+    , chk "   goto type definition" _typeDefinitionProvider (Just $ InL True)     -- BUG in lsp-test, this test fails, just change the accepted response     -- for now-    , chk "NO goto implementation"  _implementationProvider (Just $ GotoOptionsStatic True)-    , chk "   find references"          _referencesProvider  (Just True)-    , chk "   doc highlight"     _documentHighlightProvider  (Just True)-    , chk "   doc symbol"           _documentSymbolProvider  (Just True)-    , chk "   workspace symbol"    _workspaceSymbolProvider  (Just True)-    , chk "   code action"             _codeActionProvider $ Just $ CodeActionOptionsStatic True-    , chk "   code lens"                 _codeLensProvider $ Just $ CodeLensOptions Nothing-    , chk "NO doc formatting"   _documentFormattingProvider  Nothing+    , chk "NO goto implementation"  _implementationProvider (Just $ InL False)+    , chk "   find references"          _referencesProvider (Just $ InL True)+    , chk "   doc highlight"     _documentHighlightProvider (Just $ InL True)+    , chk "   doc symbol"           _documentSymbolProvider (Just $ InL True)+    , chk "   workspace symbol"    _workspaceSymbolProvider (Just True)+    , chk "   code action"             _codeActionProvider  (Just $ InL True)+    , chk "   code lens"                 _codeLensProvider  (Just $ CodeLensOptions (Just False) (Just False))+    , chk "NO doc formatting"   _documentFormattingProvider (Just $ InL False)     , chk "NO doc range formatting"-                           _documentRangeFormattingProvider  Nothing+                           _documentRangeFormattingProvider (Just $ InL False)     , chk "NO doc formatting on typing"-                          _documentOnTypeFormattingProvider  Nothing-    , chk "NO renaming"                     _renameProvider (Just $ RenameOptionsStatic False)-    , chk "NO doc link"               _documentLinkProvider  Nothing-    , chk "NO color"                         _colorProvider (Just $ ColorOptionsStatic False)-    , chk "NO folding range"          _foldingRangeProvider (Just $ FoldingRangeOptionsStatic False)+                          _documentOnTypeFormattingProvider Nothing+    , chk "NO renaming"                     _renameProvider (Just $ InL False)+    , chk "NO doc link"               _documentLinkProvider Nothing+    , chk "NO color"                         _colorProvider (Just $ InL False)+    , chk "NO folding range"          _foldingRangeProvider (Just $ InL False)     , che "   execute command"      _executeCommandProvider [blockCommandId, extendImportCommandId, typeLensCommandId]-    , chk "   workspace"                         _workspace (Just $ WorkspaceOptions (Just WorkspaceFolderOptions{_supported = Just True, _changeNotifications = Just ( WorkspaceFolderChangeNotificationsBool True )}))-    , chk "NO experimental"                   _experimental  Nothing+    , chk "   workspace"                         _workspace (Just $ WorkspaceServerCapabilities (Just WorkspaceFoldersServerCapabilities{_supported = Just True, _changeNotifications = Just ( InR True )}))+    , chk "NO experimental"                   _experimental Nothing     ] where -      tds = Just (TDSOptions (TextDocumentSyncOptions+      tds = Just (InL (TextDocumentSyncOptions                               { _openClose = Just True                               , _change    = Just TdSyncIncremental                               , _willSave  = Nothing                               , _willSaveWaitUntil = Nothing-                              , _save = Just (SaveOptions {_includeText = Nothing})}))+                              , _save = Just (InR $ SaveOptions {_includeText = Nothing})})) -      chk :: (Eq a, Show a) => TestName -> (InitializeResponseCapabilitiesInner -> a) -> a -> TestTree+      chk :: (Eq a, Show a) => TestName -> (ServerCapabilities -> a) -> a -> TestTree       chk title getActual expected =         testCase title $ getInitializeResponse >>= \ir -> expected @=? (getActual . innerCaps) ir -      che :: TestName -> (InitializeResponseCapabilitiesInner -> Maybe ExecuteCommandOptions) -> [T.Text] -> TestTree+      che :: TestName -> (ServerCapabilities -> Maybe ExecuteCommandOptions) -> [T.Text] -> TestTree       che title getActual expected = testCase title doTest         where             doTest = do@@ -181,15 +194,14 @@                 let Just ExecuteCommandOptions {_commands = List commands} = getActual $ innerCaps ir                 zipWithM_ (\e o -> T.isSuffixOf e o @? show (e,o)) expected commands --  innerCaps :: InitializeResponse -> InitializeResponseCapabilitiesInner-  innerCaps (ResponseMessage _ _ (Right (InitializeResponseCapabilities c))) = c-  innerCaps  _ = error "this test only expects inner capabilities"+  innerCaps :: ResponseMessage Initialize -> ServerCapabilities+  innerCaps (ResponseMessage _ _ (Right (InitializeResult c _))) = c+  innerCaps (ResponseMessage _ _ (Left _)) = error "Initialization error" -  acquire :: IO InitializeResponse+  acquire :: IO (ResponseMessage Initialize)   acquire = run initializeResponse -  release :: InitializeResponse -> IO ()+  release :: ResponseMessage Initialize -> IO ()   release = const $ pure ()  @@ -209,8 +221,8 @@   , testSessionWait "introduce syntax error" $ do       let content = T.unlines [ "module Testing where" ]       doc <- createDoc "Testing.hs" "haskell" content-      void $ skipManyTill anyMessage (message :: Session WorkDoneProgressCreateRequest)-      void $ skipManyTill anyMessage (message :: Session WorkDoneProgressBeginNotification)+      void $ skipManyTill anyMessage (message SWindowWorkDoneProgressCreate)+      waitForProgressBegin       let change = TextDocumentContentChangeEvent             { _range = Just (Range (Position 0 15) (Position 0 18))             , _rangeLength = Nothing@@ -488,9 +500,8 @@               in filePathToUri (joinDrive (lower drive) suffix)           let itemA = TextDocumentItem uriA "haskell" 0 aContent           let a = TextDocumentIdentifier uriA-          sendNotification TextDocumentDidOpen (DidOpenTextDocumentParams itemA)-          diagsNot <- skipManyTill anyMessage diagnostic-          let PublishDiagnosticsParams fileUri diags = _params (diagsNot :: PublishDiagnosticsNotification)+          sendNotification STextDocumentDidOpen (DidOpenTextDocumentParams itemA)+          NotificationMessage{_params = PublishDiagnosticsParams fileUri _ diags} <- skipManyTill anyMessage diagnostic           -- Check that if we put a lower-case drive in for A.A           -- the diagnostics for A.B will also be lower-case.           liftIO $ fileUri @?= uriB@@ -693,6 +704,7 @@   , removeImportTests   , extendImportTests   , suggestImportTests+  , suggestHideShadowTests   , suggestImportDisambiguationTests   , disableWarningTests   , fixConstructorImportTests@@ -727,7 +739,7 @@   [ testSession' "workspace files" $ \sessionDir -> do       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+      watchedFileRegs <- getWatchedFilesSubscriptionsUntil STextDocumentPublishDiagnostics        -- Expect 1 subscription: we only ever send one       liftIO $ length watchedFileRegs @?= 1@@ -736,7 +748,7 @@       tmpDir <- liftIO getTemporaryDirectory       liftIO $ writeFile (sessionDir </> "hie.yaml") ("cradle: {direct: {arguments: [\"-i" <> tmpDir <> "\", \"A\", \"WatchedFilesMissingModule\"]}}")       _doc <- createDoc "A.hs" "haskell" "{-# LANGUAGE NoImplicitPrelude#-}\nmodule A where\nimport WatchedFilesMissingModule"-      watchedFileRegs <- getWatchedFilesSubscriptionsUntil @PublishDiagnosticsNotification+      watchedFileRegs <- getWatchedFilesSubscriptionsUntil STextDocumentPublishDiagnostics        -- Expect 1 subscription: we only ever send one       liftIO $ length watchedFileRegs @?= 1@@ -803,7 +815,7 @@       doc <- createDoc "Testing.hs" "haskell" content       _ <- waitForDiagnostics       actionsOrCommands <- getCodeActions doc (Range (Position 3 12) (Position 3 20))-      [fixTypo] <- pure [action | CACodeAction action@CodeAction{ _title = actionTitle } <- actionsOrCommands, "monus" `T.isInfixOf` actionTitle ]+      [fixTypo] <- pure [action | InR action@CodeAction{ _title = actionTitle } <- actionsOrCommands, "monus" `T.isInfixOf` actionTitle ]       executeCodeAction fixTypo       contentAfterAction <- documentContents doc       let expectedContentAfterAction = T.unlines@@ -826,7 +838,7 @@       doc <- createDoc "Testing.hs" "haskell" content       _ <- waitForDiagnostics       actionsOrCommands <- getCodeActions doc (Range (Position 2 1) (Position 2 10))-      let [addSignature] = [action | CACodeAction action@CodeAction { _title = actionTitle } <- actionsOrCommands+      let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands                                    , "Use type signature" `T.isInfixOf` actionTitle                            ]       executeCodeAction addSignature@@ -846,7 +858,7 @@       doc <- createDoc "Testing.hs" "haskell" content       _ <- waitForDiagnostics       actionsOrCommands <- getCodeActions doc (Range (Position 2 1) (Position 2 10))-      let [addSignature] = [action | CACodeAction action@CodeAction { _title = actionTitle } <- actionsOrCommands+      let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands                                     , "Use type signature" `T.isInfixOf` actionTitle                               ]       executeCodeAction addSignature@@ -869,7 +881,7 @@       doc <- createDoc "Testing.hs" "haskell" content       _ <- waitForDiagnostics       actionsOrCommands <- getCodeActions doc (Range (Position 4 1) (Position 4 10))-      let [addSignature] = [action | CACodeAction action@CodeAction { _title = actionTitle } <- actionsOrCommands+      let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands                                     , "Use type signature" `T.isInfixOf` actionTitle                               ]       executeCodeAction addSignature@@ -1101,7 +1113,7 @@   ]   where     caWithTitle t = \case-      CACodeAction a@CodeAction{_title} -> guard (_title == t) >> Just a+      InR  a@CodeAction{_title} -> guard (_title == t) >> Just a       _ -> Nothing  extendImportTests :: TestTree@@ -1357,7 +1369,7 @@         codeActionTitle CodeAction{_title=x} = x          template setUpModules moduleUnderTest range expectedTitles expectedContentB = do-            sendNotification WorkspaceDidChangeConfiguration+            sendNotification SWorkspaceDidChangeConfiguration                 (DidChangeConfigurationParams $ toJSON                   def{checkProject = overrideCheckProject}) @@ -1365,12 +1377,12 @@             mapM_ (\x -> createDoc (fst x) "haskell" (snd x)) setUpModules             docB <- createDoc (fst moduleUnderTest) "haskell" (snd moduleUnderTest)             _  <- waitForDiagnostics-            void (skipManyTill anyMessage message :: Session WorkDoneProgressEndNotification)+            waitForProgressDone             actionsOrCommands <- getCodeActions docB range             let codeActions =                   filter                     (T.isPrefixOf "Add" . codeActionTitle)-                    [ca | CACodeAction ca <- actionsOrCommands]+                    [ca | InR ca <- actionsOrCommands]                 actualTitles = codeActionTitle <$> codeActions             -- Note that we are not testing the order of the actions, as the             -- order of the expected actions indicates which one we'll execute@@ -1460,7 +1472,7 @@           cradle = "cradle: {direct: {arguments: [-hide-all-packages, -package, base, -package, text, -package-env, -, A, Bar, Foo]}}"       liftIO $ writeFileUTF8 (dir </> "hie.yaml") cradle       doc <- createDoc "Test.hs" "haskell" before-      void (skipManyTill anyMessage message :: Session WorkDoneProgressEndNotification)+      waitForProgressDone       _diags <- waitForDiagnostics       -- there isn't a good way to wait until the whole project is checked atm       when waitForCheckProject $ liftIO $ sleep 0.5@@ -1474,7 +1486,7 @@              contentAfterAction <- documentContents doc              liftIO $ after @=? contentAfterAction           else-              liftIO $ [_title | CACodeAction CodeAction{_title} <- actions, _title == newImp ] @?= []+              liftIO $ [_title | InR CodeAction{_title} <- actions, _title == newImp ] @?= []  suggestImportDisambiguationTests :: TestTree suggestImportDisambiguationTests = testGroup "suggest import disambiguation actions"@@ -1524,13 +1536,13 @@             assertBool "EVec.fromList must not be suggested" $                 "Replace with qualified: EVec.fromList" `notElem`                 [ actionTitle-                | CACodeAction CodeAction { _title = actionTitle } <- actions+                | InR CodeAction { _title = actionTitle } <- actions                 ]         liftIO $             assertBool "EVec.++ must not be suggested" $                 "Replace with qualified: EVec.++" `notElem`                 [ actionTitle-                | CACodeAction CodeAction { _title = actionTitle } <- actions+                | InR CodeAction { _title = actionTitle } <- actions                 ]     , testGroup "fromList"         [ testCase "EVec" $@@ -1577,8 +1589,7 @@         liftIO $ mapM_ (\fp -> copyFile (hidingDir </> fp) $ dir </> fp)             $ file : auxFiles         doc <- openDoc file "haskell"-        void (skipManyTill anyMessage message-            :: Session WorkDoneProgressEndNotification)+        waitForProgressDone         void $ expectDiagnostics [(file, [(DsError, loc, "Ambiguous occurrence") | loc <- locs])]         contents <- documentContents doc         let range = Range (Position 0 0) (Position (length $ T.lines contents) 0)@@ -1586,6 +1597,193 @@         k doc actions     withHideFunction = withTarget ("HideFunction" <.> "hs") +suggestHideShadowTests :: TestTree+suggestHideShadowTests =+  testGroup+    "suggest hide shadow"+    [ testGroup+        "single"+        [ testOneCodeAction+            "hide unsued"+            "Hide on from Data.Function"+            (1, 2)+            (1, 4)+            [ "import Data.Function"+            , "f on = on"+            , "g on = on"+            ]+            [ "import Data.Function hiding (on)"+            , "f on = on"+            , "g on = on"+            ]+        , testOneCodeAction+            "extend hiding unsued"+            "Hide on from Data.Function"+            (1, 2)+            (1, 4)+            [ "import Data.Function hiding ((&))"+            , "f on = on"+            ]+            [ "import Data.Function hiding (on, (&))"+            , "f on = on"+            ]+        , testOneCodeAction+            "delete unsued"+            "Hide on from Data.Function"+            (1, 2)+            (1, 4)+            [ "import Data.Function ((&), on)"+            , "f on = on"+            ]+            [ "import Data.Function ((&))"+            , "f on = on"+            ]+        , testOneCodeAction+            "hide operator"+            "Hide & from Data.Function"+            (1, 2)+            (1, 5)+            [ "import Data.Function"+            , "f (&) = (&)"+            ]+            [ "import Data.Function hiding ((&))"+            , "f (&) = (&)"+            ]+        , testOneCodeAction+            "remove operator"+            "Hide & from Data.Function"+            (1, 2)+            (1, 5)+            [ "import Data.Function ((&), on)"+            , "f (&) = (&)"+            ]+            [ "import Data.Function ( on)"+            , "f (&) = (&)"+            ]+        , noCodeAction+            "don't remove already used"+            (2, 2)+            (2, 4)+            [ "import Data.Function"+            , "g = on"+            , "f on = on"+            ]+        ]+    , testGroup+        "multi"+        [ testOneCodeAction+            "hide from B"+            "Hide ++ from B"+            (2, 2)+            (2, 6)+            [ "import B"+            , "import C"+            , "f (++) = (++)"+            ]+            [ "import B hiding ((++))"+            , "import C"+            , "f (++) = (++)"+            ]+        , testOneCodeAction+            "hide from C"+            "Hide ++ from C"+            (2, 2)+            (2, 6)+            [ "import B"+            , "import C"+            , "f (++) = (++)"+            ]+            [ "import B"+            , "import C hiding ((++))"+            , "f (++) = (++)"+            ]+        , testOneCodeAction+            "hide from Prelude"+            "Hide ++ from Prelude"+            (2, 2)+            (2, 6)+            [ "import B"+            , "import C"+            , "f (++) = (++)"+            ]+            [ "import B"+            , "import C"+            , "import Prelude hiding ((++))"+            , "f (++) = (++)"+            ]+        , testMultiCodeActions+            "manual hide all"+            [ "Hide ++ from Prelude"+            , "Hide ++ from C"+            , "Hide ++ from B"+            ]+            (2, 2)+            (2, 6)+            [ "import B"+            , "import C"+            , "f (++) = (++)"+            ]+            [ "import B hiding ((++))"+            , "import C hiding ((++))"+            , "import Prelude hiding ((++))"+            , "f (++) = (++)"+            ]+        , testOneCodeAction+            "auto hide all"+            "Hide ++ from all occurence imports"+            (2, 2)+            (2, 6)+            [ "import B"+            , "import C"+            , "f (++) = (++)"+            ]+            [ "import B hiding ((++))"+            , "import C hiding ((++))"+            , "import Prelude hiding ((++))"+            , "f (++) = (++)"+            ]+        ]+    ]+ where+  testOneCodeAction testName actionName start end origin expected =+    helper testName start end origin expected $ \cas -> do+      action <- liftIO $ pickActionWithTitle actionName cas+      executeCodeAction action+  noCodeAction testName start end origin =+    helper testName start end origin origin $ \cas -> do+      liftIO $ cas @?= []+  testMultiCodeActions testName actionNames start end origin expected =+    helper testName start end origin expected $ \cas -> do+      let r = [ca | (InR ca) <- cas, ca ^. L.title `elem` actionNames]+      liftIO $+        (length r == length actionNames)+          @? "Expected " <> show actionNames <> ", but got " <> show cas <> " which is not its superset"+      forM_ r executeCodeAction+  helper testName (line1, col1) (line2, col2) origin expected k = testSession testName $ do+    void $ createDoc "B.hs" "haskell" $ T.unlines docB+    void $ createDoc "C.hs" "haskell" $ T.unlines docC+    doc <- createDoc "A.hs" "haskell" $ T.unlines (header <> origin)+    void waitForDiagnostics+    waitForProgressDone+    cas <- getCodeActions doc (Range (Position (line1 + length header) col1) (Position (line2 + length header) col2))+    void $ k [x | x@(InR ca) <- cas, "Hide" `T.isPrefixOf` (ca ^. L.title)]+    contentAfter <- documentContents doc+    liftIO $ contentAfter @?= T.unlines (header <> expected)+  header =+    [ "{-# OPTIONS_GHC -Wname-shadowing #-}"+    , "module A where"+    , ""+    ]+  -- for multi group+  docB =+    [ "module B where"+    , "(++) = id"+    ]+  docC =+    [ "module C where"+    , "(++) = id"+    ]+ disableWarningTests :: TestTree disableWarningTests =   testGroup "disable warnings" $@@ -1634,8 +1832,8 @@             liftIO $ expectedContent @=? contentAfterAction  where   caResultToCodeAct = \case-    CACommand _ -> Nothing-    CACodeAction c -> Just c+    InL _ -> Nothing+    InR c -> Just c  insertNewDefinitionTests :: TestTree insertNewDefinitionTests = testGroup "insert new definition actions"@@ -1651,8 +1849,8 @@             ]       docB <- createDoc "ModuleB.hs" "haskell" (T.unlines $ txtB ++ txtB')       _ <- waitForDiagnostics-      CACodeAction action@CodeAction { _title = actionTitle } : _-                  <- sortOn (\(CACodeAction CodeAction{_title=x}) -> x) <$>+      InR action@CodeAction { _title = actionTitle } : _+                  <- sortOn (\(InR CodeAction{_title=x}) -> x) <$>                      getCodeActions docB (R 1 0 1 50)       liftIO $ actionTitle @?= "Define select :: [Bool] -> Bool"       executeCodeAction action@@ -1675,8 +1873,8 @@             ]       docB <- createDoc "ModuleB.hs" "haskell" (T.unlines $ txtB ++ txtB')       _ <- waitForDiagnostics-      CACodeAction action@CodeAction { _title = actionTitle } : _-                  <- sortOn (\(CACodeAction CodeAction{_title=x}) -> x) <$>+      InR action@CodeAction { _title = actionTitle } : _+                  <- sortOn (\(InR CodeAction{_title=x}) -> x) <$>                      getCodeActions docB (R 1 0 1 50)       liftIO $ actionTitle @?= "Define select :: [Bool] -> Bool"       executeCodeAction action@@ -1993,8 +2191,8 @@       _docA <- createDoc "ModuleA.hs" "haskell" contentA       docB  <- createDoc "ModuleB.hs" "haskell" contentB       _diags <- waitForDiagnostics-      CACodeAction action@CodeAction { _title = actionTitle } : _-                  <- sortOn (\(CACodeAction CodeAction{_title=x}) -> x) <$>+      InR action@CodeAction { _title = actionTitle } : _+                  <- sortOn (\(InR CodeAction{_title=x}) -> x) <$>                      getCodeActions docB range       liftIO $ expectedAction @=? actionTitle       executeCodeAction action@@ -2013,7 +2211,7 @@       doc <- createDoc "Testing.hs" "haskell" content       _ <- waitForDiagnostics       actionsOrCommands <- getCodeActions doc (Range (Position 2 8) (Position 2 16))-      let [changeToMap] = [action | CACodeAction action@CodeAction{ _title = actionTitle } <- actionsOrCommands, ("Data." <> modname) `T.isInfixOf` actionTitle ]+      let [changeToMap] = [action | InR action@CodeAction{ _title = actionTitle } <- actionsOrCommands, ("Data." <> modname) `T.isInfixOf` actionTitle ]       executeCodeAction changeToMap       contentAfterAction <- documentContents doc       let expectedContentAfterAction = T.unlines@@ -2392,7 +2590,7 @@       $ all isDisableWarningAction actionsOrCommands     where       isDisableWarningAction = \case-        CACodeAction CodeAction{_title} -> "Disable" `T.isPrefixOf` _title && "warnings" `T.isSuffixOf` _title+        InR CodeAction{_title} -> "Disable" `T.isPrefixOf` _title && "warnings" `T.isSuffixOf` _title         _ -> False    in testGroup "remove redundant function constraints"@@ -2723,7 +2921,7 @@       contentAfterAction <- documentContents doc       liftIO $ content @=? contentAfterAction     Nothing ->-      liftIO $ [_title | CACodeAction CodeAction{_title} <- actions, _title == expectedAction ] @?= []+      liftIO $ [_title | InR CodeAction{_title} <- actions, _title == expectedAction ] @?= []  removeExportTests :: TestTree removeExportTests = testGroup "remove export actions"@@ -2917,7 +3115,7 @@     doc <- createDoc "Sigs.hs" "haskell" originalCode     [CodeLens {_command = Just c}] <- getCodeLenses doc     executeCommand c-    modifiedCode <- getDocumentEdit doc+    modifiedCode <- skipManyTill anyMessage (getDocumentEdit doc)     liftIO $ expectedCode @=? modifiedCode   in   testGroup "add signature"@@ -2937,9 +3135,11 @@         ]     ] -checkDefs :: [Location] -> Session [Expect] -> Session ()-checkDefs defs mkExpectations = traverse_ check =<< mkExpectations where+linkToLocation :: [LocationLink] -> [Location]+linkToLocation = map (\LocationLink{_targetUri,_targetRange} -> Location _targetUri _targetRange) +checkDefs :: [Location] |? [LocationLink] -> Session [Expect] -> Session ()+checkDefs (either id linkToLocation . toEither -> defs) mkExpectations = traverse_ check =<< mkExpectations where   check (ExpectRange expectedRange) = do     assertNDefinitionsFound 1 defs     assertRangeCorrect (head defs) expectedRange@@ -2966,6 +3166,7 @@ findDefinitionAndHoverTests :: TestTree findDefinitionAndHoverTests = let +  tst :: (TextDocumentIdentifier -> Position -> Session a, a -> Session [Expect] -> Session ()) -> Position -> Session [Expect] -> String -> TestTree   tst (get, check) pos targetRange title = testSessionWithExtraFiles "hover" title $ \dir -> do      -- Dirty the cache to check that definitions work even in the presence of iface files@@ -2977,7 +3178,7 @@       closeDoc fooDoc      doc <- openTestDataDoc (dir </> sourceFilePath)-    void (skipManyTill anyMessage message :: Session WorkDoneProgressEndNotification)+    waitForProgressDone     found <- get doc pos     check found targetRange @@ -3148,7 +3349,6 @@  pluginSimpleTests :: TestTree pluginSimpleTests =-  ignoreTest8101 "GHC #18070" $   ignoreInWindowsForGHC88And810 $   testSessionWithExtraFiles "plugin" "simple plugin" $ \dir -> do     _ <- openDoc (dir </> "KnownNat.hs") "haskell"@@ -3163,7 +3363,6 @@  pluginParsedResultTests :: TestTree pluginParsedResultTests =-  ignoreTest8101 "GHC #18070" $   ignoreInWindowsForGHC88And810 $   testSessionWithExtraFiles "plugin" "parsedResultAction plugin" $ \dir -> do     _ <- openDoc (dir</> "RecordDot.hs") "haskell"@@ -3436,7 +3635,7 @@ completionCommandTest name src pos wanted expected = testSession name $ do   docId <- createDoc "A.hs" "haskell" (T.unlines src)   _ <- waitForDiagnostics-  compls <- getCompletions docId pos+  compls <- skipManyTill anyMessage (getCompletions docId pos)   let wantedC = find ( \case             CompletionItem {_insertText = Just x} -> wanted `T.isPrefixOf` x             _ -> False@@ -3449,10 +3648,11 @@       executeCommand c       if src /= expected           then do-            modifiedCode <- getDocumentEdit docId+            void $ skipManyTill anyMessage loggingNotification+            modifiedCode <- skipManyTill anyMessage (getDocumentEdit docId)             liftIO $ modifiedCode @?= T.unlines expected           else do-            expectMessages @ApplyWorkspaceEditRequest 1 $ \edit ->+            expectMessages SWorkspaceApplyEdit 1 $ \edit ->               liftIO $ assertFailure $ "Expected no edit but got: " <> show edit  completionNoCommandTest ::@@ -3762,6 +3962,27 @@       (Position 3 11)       [("Integer", CiStruct, "Integer ", True, True, Nothing)], +    testSession "duplicate record fields" $ do+      void $+        createDoc "B.hs" "haskell" $+          T.unlines+            [ "{-# LANGUAGE DuplicateRecordFields #-}",+              "module B where",+              "newtype Foo = Foo { member :: () }",+              "newtype Bar = Bar { member :: () }"+            ]+      docA <-+        createDoc "A.hs" "haskell" $+          T.unlines+            [ "module A where",+              "import B",+              "memb"+            ]+      _ <- waitForDiagnostics+      compls <- getCompletions docA $ Position 2 4+      let compls' = [txt | CompletionItem {_insertText = Just txt, ..} <- compls, _label == "member"]+      liftIO $ compls' @?= ["member ${1:Foo}", "member ${1:Bar}"],+     testSessionWait "maxCompletions" $ do         doc <- createDoc "A.hs" "haskell" $ T.unlines             [ "{-# OPTIONS_GHC -Wunused-binds #-}",@@ -3779,7 +4000,7 @@     doc <- createDoc "A.hs" "haskell" source     _ <- waitForDiagnostics     highlights <- getHighlights doc (Position 3 2)-    liftIO $ highlights @?=+    liftIO $ highlights @?= List             [ DocumentHighlight (R 2 0 2 3) (Just HkRead)             , DocumentHighlight (R 3 0 3 3) (Just HkWrite)             , DocumentHighlight (R 4 6 4 9) (Just HkRead)@@ -3789,7 +4010,7 @@     doc <- createDoc "A.hs" "haskell" source     _ <- waitForDiagnostics     highlights <- getHighlights doc (Position 2 8)-    liftIO $ highlights @?=+    liftIO $ highlights @?= List             [ DocumentHighlight (R 2 7 2 10) (Just HkRead)             , DocumentHighlight (R 3 11 3 14) (Just HkRead)             ]@@ -3797,7 +4018,7 @@     doc <- createDoc "A.hs" "haskell" source     _ <- waitForDiagnostics     highlights <- getHighlights doc (Position 6 5)-    liftIO $ highlights @?=+    liftIO $ highlights @?= List             [ DocumentHighlight (R 6 4 6 7) (Just HkWrite)             , DocumentHighlight (R 6 10 6 13) (Just HkRead)             , DocumentHighlight (R 7 12 7 15) (Just HkRead)@@ -3806,7 +4027,7 @@     doc <- createDoc "A.hs" "haskell" recsource     _ <- waitForDiagnostics     highlights <- getHighlights doc (Position 4 15)-    liftIO $ highlights @?=+    liftIO $ highlights @?= List       -- Span is just the .. on 8.10, but Rec{..} before #if MIN_GHC_API_VERSION(8,10,0)             [ DocumentHighlight (R 4 8 4 10) (Just HkWrite)@@ -3816,7 +4037,7 @@             , DocumentHighlight (R 4 14 4 20) (Just HkRead)             ]     highlights <- getHighlights doc (Position 3 17)-    liftIO $ highlights @?=+    liftIO $ highlights @?= List             [ DocumentHighlight (R 3 17 3 23) (Just HkWrite)       -- Span is just the .. on 8.10, but Rec{..} before #if MIN_GHC_API_VERSION(8,10,0)@@ -4026,11 +4247,6 @@ xfail :: TestTree -> String -> TestTree xfail = flip expectFailBecause -ignoreTest8101 :: String -> TestTree -> TestTree-ignoreTest8101-  | GHC_API_VERSION == ("8.10.1" :: String) = ignoreTestBecause-  | otherwise = const id- ignoreInWindowsBecause :: String -> TestTree -> TestTree ignoreInWindowsBecause = if isWindows then ignoreTestBecause else (\_ x -> x) @@ -4128,7 +4344,7 @@     [testGroup "dependencies" [sessionDepsArePickedUp]     ,testGroup "ignore-fatal" [ignoreFatalWarning]     ,testGroup "loading" [loadCradleOnlyonce, retryFailedCradle]-    ,testGroup "multi"   [simpleMultiTest, simpleMultiTest2]+    ,testGroup "multi"   [simpleMultiTest, simpleMultiTest2, simpleMultiDefTest]     ,testGroup "sub-directory"   [simpleSubDirectoryTest]     ] @@ -4145,13 +4361,13 @@         implicit dir = test dir         test _dir = do             doc <- createDoc "B.hs" "haskell" "module B where\nimport Data.Foo"-            msgs <- someTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message @PublishDiagnosticsNotification))+            msgs <- someTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message STextDocumentPublishDiagnostics))             liftIO $ length msgs @?= 1             changeDoc doc [TextDocumentContentChangeEvent Nothing Nothing "module B where\nimport Data.Maybe"]-            msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message @PublishDiagnosticsNotification))+            msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message STextDocumentPublishDiagnostics))             liftIO $ length msgs @?= 0             _ <- createDoc "A.hs" "haskell" "module A where\nimport LoadCradleBar"-            msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message @PublishDiagnosticsNotification))+            msgs <- manyTill (skipManyTill anyMessage cradleLoadedMessage) (skipManyTill anyMessage (message STextDocumentPublishDiagnostics))             liftIO $ length msgs @?= 0  retryFailedCradle :: TestTree@@ -4230,7 +4446,7 @@  cradleLoadedMessage :: Session FromServerMessage cradleLoadedMessage = satisfy $ \case-        NotCustomServer (NotificationMessage _ (CustomServerMethod m) _) -> m == cradleLoadedMethod+        FromServerMess (SCustomMethod m) (NotMess _) -> m == cradleLoadedMethod         _ -> False  cradleLoadedMethod :: T.Text@@ -4288,6 +4504,28 @@     checkDefs locs (pure [fooL])     expectNoMoreDiagnostics 0.5 +-- Like simpleMultiTest but open the files in component 'a' in a seperate session+simpleMultiDefTest :: TestTree+simpleMultiDefTest = testCase "simple-multi-def-test" $ runWithExtraFiles "multi" $ \dir -> do+    let aPath = dir </> "a/A.hs"+        bPath = dir </> "b/B.hs"+    adoc <- liftIO $ runInDir dir $ do+      aSource <- liftIO $ readFileUtf8 aPath+      adoc <- createDoc aPath "haskell" aSource+      ~() <- skipManyTill anyMessage $ satisfyMaybe $ \case+        FromServerMess (SCustomMethod "ghcide/reference/ready") (NotMess NotificationMessage{_params = fp}) -> do+          A.Success fp' <- pure $ fromJSON fp+          if equalFilePath fp' aPath then pure () else Nothing+        _ -> Nothing+      closeDoc adoc+      pure adoc+    bSource <- liftIO $ readFileUtf8 bPath+    bdoc <- createDoc bPath "haskell" bSource+    locs <- getDefinitions bdoc (Position 2 7)+    let fooL = mkL (adoc ^. L.uri) 2 0 2 3+    checkDefs locs (pure [fooL])+    expectNoMoreDiagnostics 0.5+ ifaceTests :: TestTree ifaceTests = testGroup "Interface loading tests"     [ -- https://github.com/haskell/ghcide/pull/645/@@ -4351,7 +4589,7 @@     -- 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)+    sendNotification STextDocumentDidSave (DidSaveTextDocumentParams bdoc Nothing)      -- Check that the error propogates to A     expectDiagnostics@@ -4359,10 +4597,11 @@       -- Check that we wrote the interfaces for B when we saved-    lid <- sendRequest (CustomClientMethod "hidir") $ GetInterfaceFilesDir bPath-    res <- skipManyTill anyMessage $ responseForId lid+    let m = SCustomMethod "test"+    lid <- sendRequest m $ toJSON $ GetInterfaceFilesDir bPath+    res <- skipManyTill anyMessage $ responseForId m lid     liftIO $ case res of-      ResponseMessage{_result=Right hidir} -> do+      ResponseMessage{_result=Right (A.fromJSON -> A.Success hidir)} -> do         hi_exists <- doesFileExist $ hidir </> "B.hi"         assertBool ("Couldn't find B.hi in " ++ hidir) hi_exists       _ -> assertFailure $ "Got malformed response for CustomMessage hidir: " ++ show res@@ -4505,6 +4744,8 @@         assertBool "did not successfully complete 5 repetitions" $ Bench.success res         | e <- Bench.experiments         , Bench.name e /= "edit" -- the edit experiment does not ever fail+        -- the cradle experiments are way too slow+        , not ("cradle" `isInfixOf` Bench.name e)     ]  -- | checks if we use InitializeParams.rootUri for loading session@@ -4526,8 +4767,8 @@     [       testSession "command" $ do             -- Execute a command that will block forever-            let req = ExecuteCommandParams blockCommandId Nothing Nothing-            void $ sendRequest WorkspaceExecuteCommand req+            let req = ExecuteCommandParams Nothing blockCommandId Nothing+            void $ sendRequest SWorkspaceExecuteCommand 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 #-}"@@ -4535,13 +4776,13 @@               ]             void waitForDiagnostics             actions <- getCodeActions doc (Range (Position 1 0) (Position 1 0))-            liftIO $ [ _title | CACodeAction CodeAction{_title} <- actions] @=?+            liftIO $ [ _title | InR CodeAction{_title} <- actions] @=?               [ "add signature: foo :: a -> a"               , "Disable \"missing-signatures\" warnings"               ]     , testSession "request" $ do             -- Execute a custom request that will block for 1000 seconds-            void $ sendRequest (CustomClientMethod "test") $ BlockSeconds 1000+            void $ sendRequest (SCustomMethod "test") $ toJSON $ 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 #-}"@@ -4549,7 +4790,7 @@               ]             void waitForDiagnostics             actions <- getCodeActions doc (Range (Position 0 0) (Position 0 0))-            liftIO $ [ _title | CACodeAction CodeAction{_title} <- actions] @=?+            liftIO $ [ _title | InR CodeAction{_title} <- actions] @=?               [ "add signature: foo :: a -> a"               , "Disable \"missing-signatures\" warnings"               ]@@ -4558,19 +4799,15 @@  clientSettingsTest :: TestTree clientSettingsTest = testGroup "client settings handling"-    [-        testSession "ghcide does not support update config" $ do-            sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON ("" :: String)))-            logNot <- skipManyTill anyMessage loggingNotification-            isMessagePresent "Updating Not supported" [getLogMessage logNot]-    ,   testSession "ghcide restarts shake session on config changes" $ do-            void $ skipManyTill anyMessage $ message @RegisterCapabilityRequest-            sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON ("" :: String)))+    [ testSession "ghcide restarts shake session on config changes" $ do+            void $ skipManyTill anyMessage $ message SClientRegisterCapability+            sendNotification SWorkspaceDidChangeConfiguration (DidChangeConfigurationParams (toJSON ("" :: String)))             nots <- skipManyTill anyMessage $ count 3 loggingNotification             isMessagePresent "Restarting build session" (map getLogMessage nots)      ]-  where getLogMessage (NotLogMessage (NotificationMessage _ _ (LogMessageParams _ msg))) = msg+  where getLogMessage :: FromServerMessage -> T.Text+        getLogMessage (FromServerMess SWindowLogMessage (NotificationMessage _ _ (LogMessageParams _ msg))) = msg         getLogMessage _ = ""          isMessagePresent expectedMsg actualMsgs = liftIO $@@ -4698,7 +4935,7 @@     YesIncludeDeclaration     | NoExcludeDeclaration -getReferences' :: SymbolLocation -> IncludeDeclaration -> Session [Location]+getReferences' :: SymbolLocation -> IncludeDeclaration -> Session (List Location) getReferences' (file, l, c) includeDeclaration = do     doc <- openDoc file "haskell"     getReferences doc (Position l c) $ toBool includeDeclaration@@ -4711,10 +4948,11 @@   -- Initial Index   docid <- openDoc thisDoc "haskell"   let+    loop :: [FilePath] -> Session ()     loop [] = pure ()     loop docs = do       doc <- skipManyTill anyMessage $ satisfyMaybe $ \case-          NotCustomServer (NotificationMessage _ (CustomServerMethod "ghcide/reference/ready") fp) -> do+          FromServerMess (SCustomMethod "ghcide/reference/ready") (NotMess NotificationMessage{_params = fp}) -> do             A.Success fp' <- pure $ fromJSON fp             find (fp' ==) docs           _ -> Nothing@@ -4728,7 +4966,7 @@ referenceTest :: String -> SymbolLocation -> IncludeDeclaration -> [SymbolLocation] -> TestTree referenceTest name loc includeDeclaration expected =     referenceTestSession name (fst3 loc) docs $ \dir -> do-        actual <- getReferences' loc includeDeclaration+        List actual <- getReferences' loc includeDeclaration         liftIO $ actual `expectSameLocations` map (first3 (dir </>)) expected   where     docs = map fst3 expected@@ -4771,18 +5009,18 @@       -- Experimentally, 0.5s seems to be long enough to wait for any final diagnostics to appear.       ( >> expectNoMoreDiagnostics 0.5) -pickActionWithTitle :: T.Text -> [CAResult] -> IO CodeAction+pickActionWithTitle :: T.Text -> [Command |? CodeAction] -> IO CodeAction pickActionWithTitle title actions = do   assertBool ("Found no matching actions for " <> show title <> " in " <> show titles) (not $ null matches)   return $ head matches   where     titles =         [ actionTitle-        | CACodeAction CodeAction { _title = actionTitle } <- actions+        | InR CodeAction { _title = actionTitle } <- actions         ]     matches =         [ action-        | CACodeAction action@CodeAction { _title = actionTitle } <- actions+        | InR action@CodeAction { _title = actionTitle } <- actions         , title == actionTitle         ] @@ -4864,12 +5102,12 @@   let matches = sequence         [ listToMaybe           [ action-          | CACodeAction action@CodeAction { _title = actionTitle } <- actions+          | InR action@CodeAction { _title = actionTitle } <- actions           , expectedTitle `op` actionTitle]         | expectedTitle <- expectedTitles]   let msg = show             [ actionTitle-            | CACodeAction CodeAction { _title = actionTitle } <- actions+            | InR CodeAction { _title = actionTitle } <- actions             ]             ++ " " <> errMsg <> " "             ++ show expectedTitles@@ -5085,13 +5323,13 @@     | i >= Rope.rows r = error $ "Row number out of bounds: " <> show i <> "/" <> show (Rope.rows r)     | otherwise = Rope.takeWhile (/= '\n') $ fst $ Rope.splitAtLine 1 $ snd $ Rope.splitAtLine (i - 1) r -getWatchedFilesSubscriptionsUntil :: forall end . (FromJSON end, Typeable end) => Session [Maybe Value]-getWatchedFilesSubscriptionsUntil = do-      msgs <- manyTill (Just <$> message @RegisterCapabilityRequest <|> Nothing <$ anyMessage) (message @end)+getWatchedFilesSubscriptionsUntil :: forall m. SServerMethod m -> Session [DidChangeWatchedFilesRegistrationOptions]+getWatchedFilesSubscriptionsUntil m = do+      msgs <- manyTill (Just <$> message SClientRegisterCapability <|> Nothing <$ anyMessage) (message m)       return             [ args             | Just RequestMessage{_params = RegistrationParams (List regs)} <- msgs-            , Registration _id WorkspaceDidChangeWatchedFiles args <- regs+            , SomeRegistration (Registration _id SWorkspaceDidChangeWatchedFiles args) <- regs             ]  -- | Version of 'System.IO.Extra.withTempDir' that canonicalizes the path
test/src/Development/IDE/Test.hs view
@@ -2,6 +2,8 @@ -- SPDX-License-Identifier: Apache-2.0  {-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}  module Development.IDE.Test   ( Cursor@@ -20,6 +22,7 @@   , waitForAction   ) where +import qualified Data.Aeson as A import Control.Applicative.Combinators import Control.Lens hiding (List) import Control.Monad@@ -27,18 +30,15 @@ import Data.Bifunctor (second) import qualified Data.Map.Strict as Map import qualified Data.Text as T-import Language.Haskell.LSP.Test hiding (message)-import qualified Language.Haskell.LSP.Test as LspTest-import Language.Haskell.LSP.Types-import Language.Haskell.LSP.Types.Lens as Lsp+import Language.LSP.Test hiding (message)+import qualified Language.LSP.Test as LspTest+import Language.LSP.Types+import Language.LSP.Types.Lens as Lsp import System.Time.Extra import Test.Tasty.HUnit import System.Directory (canonicalizePath) import Data.Maybe (fromJust)-import Development.IDE.Plugin.Test (WaitForIdeRuleResult, TestRequest(WaitForIdeRule))-import Data.Aeson (FromJSON)-import Data.Typeable (Typeable)-+import Development.IDE.Plugin.Test (WaitForIdeRuleResult, TestRequest(..))  -- | (0-based line number, 0-based column number) type Cursor = (Int, Int)@@ -70,7 +70,7 @@ -- if any diagnostic messages arrive in that period expectNoMoreDiagnostics :: Seconds -> Session () expectNoMoreDiagnostics timeout =-  expectMessages @PublishDiagnosticsNotification timeout $ \diagsNot -> do+  expectMessages STextDocumentPublishDiagnostics timeout $ \diagsNot -> do     let fileUri = diagsNot ^. params . uri         actual = diagsNot ^. params . diagnostics     liftIO $@@ -79,31 +79,29 @@           <> " got "           <> show actual -expectMessages :: (FromJSON msg, Typeable msg) => Seconds -> (msg -> Session ()) -> Session ()-expectMessages timeout handle = do+expectMessages :: SMethod m -> Seconds -> (ServerMessage m -> Session ()) -> Session ()+expectMessages m timeout handle = do     -- Give any further diagnostic messages time to arrive.     liftIO $ sleep timeout     -- Send a dummy message to provoke a response from the server.     -- This guarantees that we have at least one message to     -- process, so message won't block or timeout.-    void $ sendRequest (CustomClientMethod "non-existent-method") ()-    handleMessages+    let cm = SCustomMethod "test"+    i <- sendRequest cm $ A.toJSON GetShakeSessionQueueCount+    go cm i   where-    handleMessages = (LspTest.message >>= handle) <|> handleCustomMethodResponse <|> ignoreOthers-    ignoreOthers = void anyMessage >> handleMessages--handleCustomMethodResponse :: Session ()-handleCustomMethodResponse =-    -- the CustomClientMethod triggers a RspCustomServer-    -- handle that and then exit-    void (LspTest.message :: Session CustomResponse)+    go cm i = handleMessages+      where+        handleMessages = (LspTest.message m >>= handle) <|> (void $ responseForId cm i) <|> ignoreOthers+        ignoreOthers = void anyMessage >> handleMessages  flushMessages :: Session () flushMessages = do-    void $ sendRequest (CustomClientMethod "non-existent-method") ()-    handleCustomMethodResponse <|> ignoreOthers+    let cm = SCustomMethod "non-existent-method"+    i <- sendRequest cm A.Null+    void (responseForId cm i) <|> ignoreOthers cm i     where-        ignoreOthers = void anyMessage >> flushMessages+        ignoreOthers cm i = skipManyTill anyMessage (responseForId cm i) >> flushMessages  -- | It is not possible to use 'expectDiagnostics []' to assert the absence of diagnostics, --   only that existing diagnostics have been cleared.@@ -115,7 +113,7 @@   = expectDiagnosticsWithTags   . map (second (map (\(ds, c, t) -> (ds, c, t, Nothing)))) -unwrapDiagnostic :: PublishDiagnosticsNotification -> (Uri, List Diagnostic)+unwrapDiagnostic :: NotificationMessage TextDocumentPublishDiagnostics  -> (Uri, List Diagnostic) unwrapDiagnostic diagsNot = (diagsNot^.params.uri, diagsNot^.params.diagnostics)  expectDiagnosticsWithTags :: [(String, [(DiagnosticSeverity, Cursor, T.Text, Maybe DiagnosticTag)])] -> Session ()@@ -180,8 +178,8 @@ canonicalizeUri :: Uri -> IO Uri canonicalizeUri uri = filePathToUri <$> canonicalizePath (fromJust (uriToFilePath uri)) -diagnostic :: Session PublishDiagnosticsNotification-diagnostic = LspTest.message+diagnostic :: Session (NotificationMessage TextDocumentPublishDiagnostics)+diagnostic = LspTest.message STextDocumentPublishDiagnostics  standardizeQuotes :: T.Text -> T.Text standardizeQuotes msg = let@@ -193,6 +191,11 @@  waitForAction :: String -> TextDocumentIdentifier -> Session (Either ResponseError WaitForIdeRuleResult) waitForAction key TextDocumentIdentifier{_uri} = do-    waitId <- sendRequest (CustomClientMethod "test") (WaitForIdeRule key _uri)-    ResponseMessage{_result} <- skipManyTill anyMessage $ responseForId waitId-    return _result+    let cm = SCustomMethod "test"+    waitId <- sendRequest cm (A.toJSON $ WaitForIdeRule key _uri)+    ResponseMessage{_result} <- skipManyTill anyMessage $ responseForId cm waitId+    return $ do+      e <- _result+      case A.fromJSON e of+        A.Error e -> Left $ ResponseError InternalError (T.pack e) Nothing+        A.Success a -> pure a