packages feed

ghcide 1.6.0.1 → 1.7.0.0

raw patch · 68 files changed

+4482/−2832 lines, 68 filesdep +enummapsetdep ~ghc-exactprintdep ~hie-biosdep ~hls-graphnew-uploaderPVP ok

version bump matches the API change (PVP)

Dependencies added: enummapset

Dependency ranges changed: ghc-exactprint, hie-bios, hls-graph, hls-plugin-api, prettyprinter, unliftio

API changes (from Hackage documentation)

- Development.IDE: IsHiFileStable :: IsHiFileStable
- Development.IDE: Telemetry :: Priority
- Development.IDE: data IsHiFileStable
- Development.IDE: logTelemetry :: Logger -> Text -> IO ()
- Development.IDE: prettyPrint :: Outputable a => a -> String
- Development.IDE: unsafePrintSDoc :: SDoc -> String
- Development.IDE.Core.FileStore: ($sel:getVirtualFile:VFSHandle) :: VFSHandle -> NormalizedUri -> IO (Maybe VirtualFile)
- Development.IDE.Core.FileStore: data VFSHandle
- Development.IDE.Core.FileStore: makeLSPVFSHandle :: LanguageContextEnv c -> VFSHandle
- Development.IDE.Core.FileStore: makeVFSHandle :: IO VFSHandle
- Development.IDE.Core.Rules: IsHiFileStable :: IsHiFileStable
- Development.IDE.Core.Rules: data IsHiFileStable
- Development.IDE.Core.Rules: instance Control.DeepSeq.NFData Development.IDE.Core.Rules.IsHiFileStable
- Development.IDE.Core.Rules: instance Data.Hashable.Class.Hashable Development.IDE.Core.Rules.IsHiFileStable
- Development.IDE.Core.Rules: instance GHC.Classes.Eq Development.IDE.Core.Rules.IsHiFileStable
- Development.IDE.Core.Rules: instance GHC.Generics.Generic Development.IDE.Core.Rules.IsHiFileStable
- Development.IDE.Core.Rules: instance GHC.Show.Show Development.IDE.Core.Rules.IsHiFileStable
- Development.IDE.Core.Rules: isHiFileStableRule :: Rules ()
- Development.IDE.Core.Shake: OnDiskRule :: Action ByteString -> Action (IdeResult ByteString) -> OnDiskRule
- Development.IDE.Core.Shake: VFSHandle :: (NormalizedUri -> IO (Maybe VirtualFile)) -> Maybe (NormalizedUri -> Maybe Text -> IO ()) -> VFSHandle
- Development.IDE.Core.Shake: [$sel:getHash:OnDiskRule] :: OnDiskRule -> Action ByteString
- Development.IDE.Core.Shake: [$sel:getVirtualFile:VFSHandle] :: VFSHandle -> NormalizedUri -> IO (Maybe VirtualFile)
- Development.IDE.Core.Shake: [$sel:runRule:OnDiskRule] :: OnDiskRule -> Action (IdeResult ByteString)
- Development.IDE.Core.Shake: [$sel:setVirtualFileContents:VFSHandle] :: VFSHandle -> Maybe (NormalizedUri -> Maybe Text -> IO ())
- Development.IDE.Core.Shake: [$sel:vfs:ShakeExtras] :: ShakeExtras -> VFSHandle
- Development.IDE.Core.Shake: data OnDiskRule
- Development.IDE.Core.Shake: data VFSHandle
- Development.IDE.Core.Shake: defineOnDisk :: (ShakeValue k, RuleResult k ~ ()) => (k -> NormalizedFilePath -> OnDiskRule) -> Rules ()
- Development.IDE.Core.Shake: instance Control.DeepSeq.NFData k => Control.DeepSeq.NFData (Development.IDE.Core.Shake.QDisk k)
- Development.IDE.Core.Shake: instance Data.Hashable.Class.Hashable k => Data.Hashable.Class.Hashable (Development.IDE.Core.Shake.QDisk k)
- Development.IDE.Core.Shake: instance Development.IDE.Core.Shake.IsIdeGlobal Development.IDE.Core.Shake.VFSHandle
- Development.IDE.Core.Shake: instance GHC.Classes.Eq k => GHC.Classes.Eq (Development.IDE.Core.Shake.QDisk k)
- Development.IDE.Core.Shake: instance GHC.Generics.Generic (Development.IDE.Core.Shake.QDisk k)
- Development.IDE.Core.Shake: instance GHC.Show.Show k => GHC.Show.Show (Development.IDE.Core.Shake.QDisk k)
- Development.IDE.Core.Shake: needOnDisk :: (ShakeValue k, RuleResult k ~ ()) => k -> NormalizedFilePath -> Action ()
- Development.IDE.Core.Shake: needOnDisks :: (ShakeValue k, RuleResult k ~ ()) => k -> [NormalizedFilePath] -> Action ()
- Development.IDE.GHC.Compat.Outputable: printNameWithoutUniques :: Outputable a => a -> String
- Development.IDE.GHC.Compat.Outputable: printSDocAllTheWay :: DynFlags -> SDoc -> String
- Development.IDE.GHC.ExactPrint: useAnnotatedSource :: String -> IdeState -> NormalizedFilePath -> IO (Maybe (Annotated ParsedSource))
- Development.IDE.GHC.Util: prettyPrint :: Outputable a => a -> String
- Development.IDE.GHC.Util: unsafePrintSDoc :: SDoc -> String
- Development.IDE.Main: instance Data.Default.Class.Default Development.IDE.Main.Arguments
- Development.IDE.Spans.AtPoint: getAstNamesAtPoint :: HieASTs a -> Position -> PositionMapping -> [Name]
- Development.IDE.Spans.Common: showGhc :: Outputable a => a -> Text
- Development.IDE.Spans.Common: showNameWithoutUniques :: Outputable a => a -> Text
- Development.IDE.Types.Logger: Telemetry :: Priority
- Development.IDE.Types.Logger: logTelemetry :: Logger -> Text -> IO ()
- Development.IDE.Types.Options: optShakeFiles :: IdeOptions -> Maybe FilePath
+ Development.IDE: (<+>) :: Doc ann -> Doc ann -> Doc ann
+ Development.IDE: (<>) :: Semigroup a => a -> a -> a
+ Development.IDE: AvailablePerLine :: !Int -> !Double -> PageWidth
+ Development.IDE: DataColumn :: LoggingColumn
+ Development.IDE: Deep :: FusionDepth
+ Development.IDE: LayoutOptions :: PageWidth -> LayoutOptions
+ Development.IDE: PriorityColumn :: LoggingColumn
+ Development.IDE: Recorder :: (forall m. MonadIO m => msg -> m ()) -> Recorder msg
+ Development.IDE: RuleWithOldValue :: (k -> NormalizedFilePath -> Value v -> Action (Maybe ByteString, IdeResult v)) -> RuleBody k v
+ Development.IDE: SAnnPop :: SimpleDocStream ann -> SimpleDocStream ann
+ Development.IDE: SAnnPush :: ann -> SimpleDocStream ann -> SimpleDocStream ann
+ Development.IDE: SChar :: !Char -> SimpleDocStream ann -> SimpleDocStream ann
+ Development.IDE: SEmpty :: SimpleDocStream ann
+ Development.IDE: SFail :: SimpleDocStream ann
+ Development.IDE: SLine :: !Int -> SimpleDocStream ann -> SimpleDocStream ann
+ Development.IDE: SText :: !Int -> !Text -> SimpleDocStream ann -> SimpleDocStream ann
+ Development.IDE: Shallow :: FusionDepth
+ Development.IDE: SourceLocColumn :: LoggingColumn
+ Development.IDE: ThreadIdColumn :: LoggingColumn
+ Development.IDE: TimeColumn :: LoggingColumn
+ Development.IDE: Unbounded :: PageWidth
+ Development.IDE: VFSModified :: !VFS -> VFSModified
+ Development.IDE: VFSUnmodified :: VFSModified
+ Development.IDE: WithPriority :: Priority -> CallStack -> a -> WithPriority a
+ Development.IDE: [callStack_] :: WithPriority a -> CallStack
+ Development.IDE: [hirRuntimeModules] :: HiFileResult -> !ModuleEnv UTCTime
+ Development.IDE: [layoutPageWidth] :: LayoutOptions -> PageWidth
+ Development.IDE: [logger_] :: Recorder msg -> forall m. MonadIO m => msg -> m ()
+ Development.IDE: [payload] :: WithPriority a -> a
+ Development.IDE: [priority] :: WithPriority a -> Priority
+ Development.IDE: [tmrRuntimeModules] :: TcModuleResult -> !ModuleEnv UTCTime
+ Development.IDE: align :: Doc ann -> Doc ann
+ Development.IDE: alterAnnotations :: (ann -> [ann']) -> Doc ann -> Doc ann'
+ Development.IDE: alterAnnotationsS :: (ann -> Maybe ann') -> SimpleDocStream ann -> SimpleDocStream ann'
+ Development.IDE: angles :: Doc ann -> Doc ann
+ Development.IDE: annotate :: ann -> Doc ann -> Doc ann
+ Development.IDE: backslash :: Doc ann
+ Development.IDE: braces :: Doc ann -> Doc ann
+ Development.IDE: brackets :: Doc ann -> Doc ann
+ Development.IDE: cat :: [Doc ann] -> Doc ann
+ Development.IDE: cfilter :: (a -> Bool) -> Recorder a -> Recorder a
+ Development.IDE: class Pretty a
+ Development.IDE: cmap :: (a -> b) -> Recorder b -> Recorder a
+ Development.IDE: cmapIO :: (a -> IO b) -> Recorder b -> Recorder a
+ Development.IDE: cmapWithPrio :: (a -> b) -> Recorder (WithPriority b) -> Recorder (WithPriority a)
+ Development.IDE: colon :: Doc ann
+ Development.IDE: column :: (Int -> Doc ann) -> Doc ann
+ Development.IDE: comma :: Doc ann
+ Development.IDE: concatWith :: Foldable t => (Doc ann -> Doc ann -> Doc ann) -> t (Doc ann) -> Doc ann
+ Development.IDE: data Doc ann
+ Development.IDE: data FusionDepth
+ Development.IDE: data LoggingColumn
+ Development.IDE: data PageWidth
+ Development.IDE: data SimpleDocStream ann
+ Development.IDE: data VFSModified
+ Development.IDE: data WithPriority a
+ Development.IDE: defaultLayoutOptions :: LayoutOptions
+ Development.IDE: dot :: Doc ann
+ Development.IDE: dquote :: Doc ann
+ Development.IDE: dquotes :: Doc ann -> Doc ann
+ Development.IDE: emptyDoc :: Doc ann
+ Development.IDE: enclose :: Doc ann -> Doc ann -> Doc ann -> Doc ann
+ Development.IDE: encloseSep :: Doc ann -> Doc ann -> Doc ann -> [Doc ann] -> Doc ann
+ Development.IDE: equals :: Doc ann
+ Development.IDE: fill :: Int -> Doc ann -> Doc ann
+ Development.IDE: fillBreak :: Int -> Doc ann -> Doc ann
+ Development.IDE: fillCat :: [Doc ann] -> Doc ann
+ Development.IDE: fillSep :: [Doc ann] -> Doc ann
+ Development.IDE: flatAlt :: Doc ann -> Doc ann -> Doc ann
+ Development.IDE: fuse :: FusionDepth -> Doc ann -> Doc ann
+ Development.IDE: group :: Doc ann -> Doc ann
+ Development.IDE: hang :: Int -> Doc ann -> Doc ann
+ Development.IDE: hardline :: Doc ann
+ Development.IDE: hcat :: [Doc ann] -> Doc ann
+ Development.IDE: hsep :: [Doc ann] -> Doc ann
+ Development.IDE: indent :: Int -> Doc ann -> Doc ann
+ Development.IDE: infixr 6 <+>
+ Development.IDE: langle :: Doc ann
+ Development.IDE: layoutCompact :: Doc ann1 -> SimpleDocStream ann2
+ Development.IDE: layoutPretty :: LayoutOptions -> Doc ann -> SimpleDocStream ann
+ Development.IDE: layoutSmart :: LayoutOptions -> Doc ann -> SimpleDocStream ann
+ Development.IDE: lbrace :: Doc ann
+ Development.IDE: lbracket :: Doc ann
+ Development.IDE: line :: Doc ann
+ Development.IDE: line' :: Doc ann
+ Development.IDE: list :: [Doc ann] -> Doc ann
+ Development.IDE: logWith :: (HasCallStack, MonadIO m) => Recorder (WithPriority msg) -> Priority -> msg -> m ()
+ Development.IDE: lparen :: Doc ann
+ Development.IDE: lspClientLogRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text)
+ Development.IDE: lspClientMessageRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text)
+ Development.IDE: makeDefaultStderrRecorder :: MonadIO m => Maybe [LoggingColumn] -> Priority -> m (Recorder (WithPriority (Doc a)))
+ Development.IDE: nest :: Int -> Doc ann -> Doc ann
+ Development.IDE: nesting :: (Int -> Doc ann) -> Doc ann
+ Development.IDE: newtype LayoutOptions
+ Development.IDE: newtype Recorder msg
+ Development.IDE: pageWidth :: (PageWidth -> Doc ann) -> Doc ann
+ Development.IDE: parens :: Doc ann -> Doc ann
+ Development.IDE: pipe :: Doc ann
+ Development.IDE: plural :: (Num amount, Eq amount) => doc -> doc -> amount -> doc
+ Development.IDE: pretty :: Pretty a => a -> Doc ann
+ Development.IDE: prettyList :: Pretty a => [a] -> Doc ann
+ Development.IDE: printOutputable :: Outputable a => a -> Text
+ Development.IDE: priorityToHsLoggerPriority :: Priority -> Priority
+ Development.IDE: punctuate :: Doc ann -> [Doc ann] -> [Doc ann]
+ Development.IDE: rangle :: Doc ann
+ Development.IDE: rbrace :: Doc ann
+ Development.IDE: rbracket :: Doc ann
+ Development.IDE: reAnnotate :: (ann -> ann') -> Doc ann -> Doc ann'
+ Development.IDE: reAnnotateS :: (ann -> ann') -> SimpleDocStream ann -> SimpleDocStream ann'
+ Development.IDE: removeTrailingWhitespace :: SimpleDocStream ann -> SimpleDocStream ann
+ Development.IDE: renderStrict :: SimpleDocStream ann -> Text
+ Development.IDE: rparen :: Doc ann
+ Development.IDE: semi :: Doc ann
+ Development.IDE: sep :: [Doc ann] -> Doc ann
+ Development.IDE: slash :: Doc ann
+ Development.IDE: softline :: Doc ann
+ Development.IDE: softline' :: Doc ann
+ Development.IDE: space :: Doc ann
+ Development.IDE: squote :: Doc ann
+ Development.IDE: squotes :: Doc ann -> Doc ann
+ Development.IDE: surround :: Doc ann -> Doc ann -> Doc ann -> Doc ann
+ Development.IDE: tupled :: [Doc ann] -> Doc ann
+ Development.IDE: unAnnotate :: Doc ann -> Doc xxx
+ Development.IDE: unAnnotateS :: SimpleDocStream ann -> SimpleDocStream xxx
+ Development.IDE: unsafeViaShow :: Show a => a -> Doc ann
+ Development.IDE: vcat :: [Doc ann] -> Doc ann
+ Development.IDE: viaShow :: Show a => a -> Doc ann
+ Development.IDE: vsep :: [Doc ann] -> Doc ann
+ Development.IDE: width :: Doc ann -> (Int -> Doc ann) -> Doc ann
+ Development.IDE: withBacklog :: (v -> Recorder a) -> IO (Recorder a, v -> IO ())
+ Development.IDE: withDefaultRecorder :: MonadUnliftIO m => Maybe FilePath -> Maybe [LoggingColumn] -> Priority -> (Recorder (WithPriority (Doc d)) -> m a) -> m a
+ Development.IDE.Core.Compile: RecompilationInfo :: FileVersion -> Maybe (HiFileResult, FileVersion) -> (NormalizedFilePath -> m (Maybe FileVersion)) -> (Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult)) -> RecompilationInfo m
+ Development.IDE.Core.Compile: [get_file_version] :: RecompilationInfo m -> NormalizedFilePath -> m (Maybe FileVersion)
+ Development.IDE.Core.Compile: [old_value] :: RecompilationInfo m -> Maybe (HiFileResult, FileVersion)
+ Development.IDE.Core.Compile: [regenerate] :: RecompilationInfo m -> Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult)
+ Development.IDE.Core.Compile: [source_version] :: RecompilationInfo m -> FileVersion
+ Development.IDE.Core.Compile: [tmrRuntimeModules] :: TcModuleResult -> !ModuleEnv UTCTime
+ Development.IDE.Core.Compile: data RecompilationInfo m
+ Development.IDE.Core.FileStore: LogCouldNotIdentifyReverseDeps :: !NormalizedFilePath -> Log
+ Development.IDE.Core.FileStore: LogShake :: Log -> Log
+ Development.IDE.Core.FileStore: LogTypeCheckingReverseDeps :: !NormalizedFilePath -> !Maybe [NormalizedFilePath] -> Log
+ Development.IDE.Core.FileStore: data Log
+ Development.IDE.Core.FileStore: instance GHC.Show.Show Development.IDE.Core.FileStore.Log
+ Development.IDE.Core.FileStore: instance Prettyprinter.Internal.Pretty Development.IDE.Core.FileStore.Log
+ Development.IDE.Core.FileUtils: getModTime :: FilePath -> IO POSIXTime
+ Development.IDE.Core.OfInterest: LogShake :: Log -> Log
+ Development.IDE.Core.OfInterest: data Log
+ Development.IDE.Core.OfInterest: instance GHC.Show.Show Development.IDE.Core.OfInterest.Log
+ Development.IDE.Core.OfInterest: instance Prettyprinter.Internal.Pretty Development.IDE.Core.OfInterest.Log
+ Development.IDE.Core.RuleTypes: [hirRuntimeModules] :: HiFileResult -> !ModuleEnv UTCTime
+ Development.IDE.Core.RuleTypes: [tmrRuntimeModules] :: TcModuleResult -> !ModuleEnv UTCTime
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Eq Development.IDE.Core.RuleTypes.FileVersion
+ Development.IDE.Core.RuleTypes: instance GHC.Classes.Ord Development.IDE.Core.RuleTypes.FileVersion
+ Development.IDE.Core.Rules: LogExactPrint :: Log -> Log
+ Development.IDE.Core.Rules: LogLoadingHieFile :: !NormalizedFilePath -> Log
+ Development.IDE.Core.Rules: LogLoadingHieFileFail :: !FilePath -> !SomeException -> Log
+ Development.IDE.Core.Rules: LogLoadingHieFileSuccess :: !FilePath -> Log
+ Development.IDE.Core.Rules: LogReindexingHieFile :: !NormalizedFilePath -> Log
+ Development.IDE.Core.Rules: LogShake :: Log -> Log
+ Development.IDE.Core.Rules: [$sel:templateHaskellWarning:RulesConfig] :: RulesConfig -> LspT Config IO ()
+ Development.IDE.Core.Rules: data Log
+ Development.IDE.Core.Rules: getRebuildCount :: Action Int
+ Development.IDE.Core.Rules: instance Development.IDE.Core.Shake.IsIdeGlobal Development.IDE.Core.Rules.RebuildCounter
+ Development.IDE.Core.Rules: instance GHC.Show.Show Development.IDE.Core.Rules.Log
+ Development.IDE.Core.Rules: instance Prettyprinter.Internal.Pretty Development.IDE.Core.Rules.Log
+ Development.IDE.Core.Service: LogFileExists :: Log -> Log
+ Development.IDE.Core.Service: LogOfInterest :: Log -> Log
+ Development.IDE.Core.Service: LogShake :: Log -> Log
+ Development.IDE.Core.Service: data Log
+ Development.IDE.Core.Service: instance GHC.Show.Show Development.IDE.Core.Service.Log
+ Development.IDE.Core.Service: instance Prettyprinter.Internal.Pretty Development.IDE.Core.Service.Log
+ Development.IDE.Core.Shake: LogBuildSessionFinish :: !Maybe SomeException -> Log
+ Development.IDE.Core.Shake: LogBuildSessionRestart :: !String -> ![DelayedActionInternal] -> !HashSet Key -> !Seconds -> !Maybe FilePath -> Log
+ Development.IDE.Core.Shake: LogBuildSessionRestartTakingTooLong :: !Seconds -> Log
+ Development.IDE.Core.Shake: LogCreateHieDbExportsMapFinish :: !Int -> Log
+ Development.IDE.Core.Shake: LogCreateHieDbExportsMapStart :: Log
+ Development.IDE.Core.Shake: LogDefineEarlyCutoffRuleCustomNewnessHasDiag :: !FileDiagnostic -> Log
+ Development.IDE.Core.Shake: LogDefineEarlyCutoffRuleNoDiagHasDiag :: !FileDiagnostic -> Log
+ Development.IDE.Core.Shake: LogDelayedAction :: !DelayedAction () -> !Seconds -> Log
+ Development.IDE.Core.Shake: LogDiagsDiffButNoLspEnv :: ![FileDiagnostic] -> Log
+ Development.IDE.Core.Shake: RuleWithOldValue :: (k -> NormalizedFilePath -> Value v -> Action (Maybe ByteString, IdeResult v)) -> RuleBody k v
+ Development.IDE.Core.Shake: VFSModified :: !VFS -> VFSModified
+ Development.IDE.Core.Shake: VFSUnmodified :: VFSModified
+ Development.IDE.Core.Shake: [$sel:vfsVar:ShakeExtras] :: ShakeExtras -> TVar VFS
+ Development.IDE.Core.Shake: data Log
+ Development.IDE.Core.Shake: data VFSModified
+ Development.IDE.Core.Shake: getVirtualFile :: NormalizedFilePath -> Action (Maybe VirtualFile)
+ Development.IDE.Core.Shake: instance GHC.Show.Show Development.IDE.Core.Shake.Log
+ Development.IDE.Core.Shake: instance Prettyprinter.Internal.Pretty Development.IDE.Core.Shake.Log
+ Development.IDE.GHC.Compat: ModuleTarget :: Module -> AnnTarget name
+ Development.IDE.GHC.Compat: bcoFreeNames :: UnlinkedBCO -> UniqDSet Name
+ Development.IDE.GHC.Compat: coreExprToBCOs :: HscEnv -> Module -> CoreExpr -> IO UnlinkedBCO
+ Development.IDE.GHC.Compat: corePrepExpr :: DynFlags -> HscEnv -> CoreExpr -> IO CoreExpr
+ Development.IDE.GHC.Compat: data AnnTarget name
+ Development.IDE.GHC.Compat: data Dependencies
+ Development.IDE.GHC.Compat: emptyTidyEnv :: TidyEnv
+ Development.IDE.GHC.Compat: extendAnnEnvList :: AnnEnv -> [Annotation] -> AnnEnv
+ Development.IDE.GHC.Compat: getDependentMods :: ModIface -> [ModuleName]
+ Development.IDE.GHC.Compat: hscCompileCoreExprHook :: Hooks -> Maybe (HscEnv -> SrcSpan -> CoreExpr -> IO ForeignHValue)
+ Development.IDE.GHC.Compat: icInteractiveModule :: InteractiveContext -> Module
+ Development.IDE.GHC.Compat: isObjectLinkable :: Linkable -> Bool
+ Development.IDE.GHC.Compat: linkExpr :: HscEnv -> SrcSpan -> UnlinkedBCO -> IO ForeignHValue
+ Development.IDE.GHC.Compat: lintInteractiveExpr :: String -> HscEnv -> CoreExpr -> IO ()
+ Development.IDE.GHC.Compat: lookupHpt :: HomePackageTable -> ModuleName -> Maybe HomeModInfo
+ Development.IDE.GHC.Compat: pattern Annotation :: () => CoreAnnTarget -> AnnPayload -> Annotation
+ Development.IDE.GHC.Compat: simplifyExpr :: DynFlags -> HscEnv -> CoreExpr -> IO CoreExpr
+ Development.IDE.GHC.Compat: tidyExpr :: TidyEnv -> CoreExpr -> CoreExpr
+ Development.IDE.GHC.Compat: type CoreExpr = Expr CoreBndr
+ Development.IDE.GHC.Compat: type HomePackageTable = DModuleNameEnv HomeModInfo
+ Development.IDE.GHC.Compat: type ModIfaceAnnotation = Annotation
+ Development.IDE.GHC.Compat.Core: rationalFromFractionalLit :: FractionalLit -> Rational
+ Development.IDE.GHC.Compat.Outputable: defaultUserStyle :: PprStyle
+ Development.IDE.GHC.Compat.Outputable: printWithoutUniques :: Outputable a => a -> String
+ Development.IDE.GHC.Compat.Outputable: withPprStyle :: PprStyle -> SDoc -> SDoc
+ Development.IDE.GHC.Compat.Parser: pattern EpaBlockComment :: String -> AnnotationComment
+ Development.IDE.GHC.Compat.Parser: pattern EpaLineComment :: String -> AnnotationComment
+ Development.IDE.GHC.ExactPrint: LogShake :: Log -> Log
+ Development.IDE.GHC.ExactPrint: data Log
+ Development.IDE.GHC.ExactPrint: instance GHC.Show.Show Development.IDE.GHC.ExactPrint.Log
+ Development.IDE.GHC.ExactPrint: instance Prettyprinter.Internal.Pretty Development.IDE.GHC.ExactPrint.Log
+ Development.IDE.GHC.Orphans: instance Control.DeepSeq.NFData LinkerTypes.Unlinked
+ Development.IDE.GHC.Util: printOutputable :: Outputable a => a -> Text
+ Development.IDE.Import.DependencyInformation: [rawModuleNameMap] :: RawDependencyInformation -> !FilePathIdMap ShowableModuleName
+ Development.IDE.LSP.LanguageServer: LogCancelledRequest :: !SomeLspId -> Log
+ Development.IDE.LSP.LanguageServer: LogReactorMessageActionException :: !SomeException -> Log
+ Development.IDE.LSP.LanguageServer: LogReactorThreadException :: !SomeException -> Log
+ Development.IDE.LSP.LanguageServer: LogReactorThreadStopped :: Log
+ Development.IDE.LSP.LanguageServer: LogRegisteringIdeConfig :: !IdeConfiguration -> Log
+ Development.IDE.LSP.LanguageServer: LogSession :: Log -> Log
+ Development.IDE.LSP.LanguageServer: data Log
+ Development.IDE.LSP.LanguageServer: instance GHC.Show.Show Development.IDE.LSP.LanguageServer.Log
+ Development.IDE.LSP.LanguageServer: instance Prettyprinter.Internal.Pretty Development.IDE.LSP.LanguageServer.Log
+ Development.IDE.Main: LogGhcIde :: Log -> Log
+ Development.IDE.Main: LogHeapStats :: !Log -> Log
+ Development.IDE.Main: LogLanguageServer :: Log -> Log
+ Development.IDE.Main: LogLspStart :: Log
+ Development.IDE.Main: LogLspStartDuration :: !Seconds -> Log
+ Development.IDE.Main: LogOnlyPartialGhc9Support :: Log
+ Development.IDE.Main: LogPluginHLS :: Log -> Log
+ Development.IDE.Main: LogRules :: Log -> Log
+ Development.IDE.Main: LogService :: Log -> Log
+ Development.IDE.Main: LogSession :: Log -> Log
+ Development.IDE.Main: LogSetInitialDynFlagsException :: !SomeException -> Log
+ Development.IDE.Main: LogShake :: Log -> Log
+ Development.IDE.Main: LogShouldRunSubset :: !Bool -> Log
+ Development.IDE.Main: data Log
+ Development.IDE.Main: instance GHC.Show.Show Development.IDE.Main.Log
+ Development.IDE.Main: instance Prettyprinter.Internal.Pretty Development.IDE.Main.Log
+ Development.IDE.Main.HeapStats: LogHeapStats :: !Word64 -> !Word64 -> Log
+ Development.IDE.Main.HeapStats: LogHeapStatsDisabled :: Log
+ Development.IDE.Main.HeapStats: LogHeapStatsPeriod :: !Int -> Log
+ Development.IDE.Main.HeapStats: data Log
+ Development.IDE.Main.HeapStats: instance GHC.Show.Show Development.IDE.Main.HeapStats.Log
+ Development.IDE.Main.HeapStats: instance Prettyprinter.Internal.Pretty Development.IDE.Main.HeapStats.Log
+ Development.IDE.Plugin.CodeAction.ExactPrint: wildCardSymbol :: String
+ Development.IDE.Plugin.Completions: LogShake :: Log -> Log
+ Development.IDE.Plugin.Completions: data Log
+ Development.IDE.Plugin.Completions: instance GHC.Show.Show Development.IDE.Plugin.Completions.Log
+ Development.IDE.Plugin.Completions: instance Prettyprinter.Internal.Pretty Development.IDE.Plugin.Completions.Log
+ Development.IDE.Plugin.HLS: LogNoEnabledPlugins :: Log
+ Development.IDE.Plugin.HLS: data Log
+ Development.IDE.Plugin.HLS: instance GHC.Show.Show Development.IDE.Plugin.HLS.Log
+ Development.IDE.Plugin.HLS: instance Prettyprinter.Internal.Pretty Development.IDE.Plugin.HLS.Log
+ Development.IDE.Plugin.HLS.GhcIde: LogCompletions :: Log -> Log
+ Development.IDE.Plugin.HLS.GhcIde: LogNotifications :: Log -> Log
+ Development.IDE.Plugin.HLS.GhcIde: LogTypeLenses :: Log -> Log
+ Development.IDE.Plugin.HLS.GhcIde: data Log
+ Development.IDE.Plugin.HLS.GhcIde: instance GHC.Show.Show Development.IDE.Plugin.HLS.GhcIde.Log
+ Development.IDE.Plugin.HLS.GhcIde: instance Prettyprinter.Internal.Pretty Development.IDE.Plugin.HLS.GhcIde.Log
+ Development.IDE.Plugin.Test: GetRebuildsCount :: TestRequest
+ Development.IDE.Plugin.TypeLenses: LogShake :: Log -> Log
+ Development.IDE.Plugin.TypeLenses: data Log
+ Development.IDE.Plugin.TypeLenses: instance GHC.Show.Show Development.IDE.Plugin.TypeLenses.Log
+ Development.IDE.Plugin.TypeLenses: instance Prettyprinter.Internal.Pretty Development.IDE.Plugin.TypeLenses.Log
+ Development.IDE.Session: LogCradle :: !Cradle Void -> Log
+ Development.IDE.Session: LogCradleNotFound :: !FilePath -> Log
+ Development.IDE.Session: LogCradlePath :: !FilePath -> Log
+ Development.IDE.Session: LogDLLLoadError :: !String -> Log
+ Development.IDE.Session: LogGetInitialGhcLibDirDefaultCradleFail :: !CradleError -> !FilePath -> !Maybe FilePath -> !Cradle Void -> Log
+ Development.IDE.Session: LogGetInitialGhcLibDirDefaultCradleNone :: Log
+ Development.IDE.Session: LogHieDbRetriesExhausted :: !Int -> !Int -> !Int -> !SomeException -> Log
+ Development.IDE.Session: LogHieDbRetry :: !Int -> !Int -> !Int -> !SomeException -> Log
+ Development.IDE.Session: LogHieDbWriterThreadException :: !SomeException -> Log
+ Development.IDE.Session: LogHieDbWriterThreadSQLiteError :: !SQLError -> Log
+ Development.IDE.Session: LogInterfaceFilesCacheDir :: !FilePath -> Log
+ Development.IDE.Session: LogKnownFilesUpdated :: !HashMap Target (HashSet NormalizedFilePath) -> Log
+ Development.IDE.Session: LogMakingNewHscEnv :: ![UnitId] -> Log
+ Development.IDE.Session: LogNewComponentCache :: !(([FileDiagnostic], Maybe HscEnvEq), DependencyInfo) -> Log
+ Development.IDE.Session: LogNoneCradleFound :: FilePath -> Log
+ Development.IDE.Session: LogSessionLoadingResult :: !Either [CradleError] (ComponentOptions, FilePath) -> Log
+ Development.IDE.Session: LogSettingInitialDynFlags :: Log
+ Development.IDE.Session: data Log
+ Development.IDE.Session: instance GHC.Show.Show Development.IDE.Session.Log
+ Development.IDE.Session: instance Prettyprinter.Internal.Pretty Development.IDE.Session.Log
+ Development.IDE.Spans.AtPoint: getNamesAtPoint :: HieASTs a -> Position -> PositionMapping -> [Name]
+ Development.IDE.Spans.Pragmas: insertNewPragma :: NextPragmaInfo -> Extension -> TextEdit
+ Development.IDE.Types.Logger: DataColumn :: LoggingColumn
+ Development.IDE.Types.Logger: PriorityColumn :: LoggingColumn
+ Development.IDE.Types.Logger: Recorder :: (forall m. MonadIO m => msg -> m ()) -> Recorder msg
+ Development.IDE.Types.Logger: SourceLocColumn :: LoggingColumn
+ Development.IDE.Types.Logger: ThreadIdColumn :: LoggingColumn
+ Development.IDE.Types.Logger: TimeColumn :: LoggingColumn
+ Development.IDE.Types.Logger: WithPriority :: Priority -> CallStack -> a -> WithPriority a
+ Development.IDE.Types.Logger: [callStack_] :: WithPriority a -> CallStack
+ Development.IDE.Types.Logger: [logger_] :: Recorder msg -> forall m. MonadIO m => msg -> m ()
+ Development.IDE.Types.Logger: [payload] :: WithPriority a -> a
+ Development.IDE.Types.Logger: [priority] :: WithPriority a -> Priority
+ Development.IDE.Types.Logger: cfilter :: (a -> Bool) -> Recorder a -> Recorder a
+ Development.IDE.Types.Logger: cmap :: (a -> b) -> Recorder b -> Recorder a
+ Development.IDE.Types.Logger: cmapIO :: (a -> IO b) -> Recorder b -> Recorder a
+ Development.IDE.Types.Logger: cmapWithPrio :: (a -> b) -> Recorder (WithPriority b) -> Recorder (WithPriority a)
+ Development.IDE.Types.Logger: data LoggingColumn
+ Development.IDE.Types.Logger: data WithPriority a
+ Development.IDE.Types.Logger: instance Data.Functor.Contravariant.Contravariant Development.IDE.Types.Logger.Recorder
+ Development.IDE.Types.Logger: instance GHC.Base.Functor Development.IDE.Types.Logger.WithPriority
+ Development.IDE.Types.Logger: instance GHC.Base.Monoid (Development.IDE.Types.Logger.Recorder msg)
+ Development.IDE.Types.Logger: instance GHC.Base.Semigroup (Development.IDE.Types.Logger.Recorder msg)
+ Development.IDE.Types.Logger: logWith :: (HasCallStack, MonadIO m) => Recorder (WithPriority msg) -> Priority -> msg -> m ()
+ Development.IDE.Types.Logger: lspClientLogRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text)
+ Development.IDE.Types.Logger: lspClientMessageRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text)
+ Development.IDE.Types.Logger: makeDefaultStderrRecorder :: MonadIO m => Maybe [LoggingColumn] -> Priority -> m (Recorder (WithPriority (Doc a)))
+ Development.IDE.Types.Logger: newtype Recorder msg
+ Development.IDE.Types.Logger: priorityToHsLoggerPriority :: Priority -> Priority
+ Development.IDE.Types.Logger: renderStrict :: SimpleDocStream ann -> Text
+ Development.IDE.Types.Logger: withBacklog :: (v -> Recorder a) -> IO (Recorder a, v -> IO ())
+ Development.IDE.Types.Logger: withDefaultRecorder :: MonadUnliftIO m => Maybe FilePath -> Maybe [LoggingColumn] -> Priority -> (Recorder (WithPriority (Doc d)) -> m a) -> m a
+ Text.Fuzzy.Parallel: Scored :: !Int -> !a -> Scored a
+ Text.Fuzzy.Parallel: [original] :: Scored a -> !a
+ Text.Fuzzy.Parallel: [score] :: Scored a -> !Int
+ Text.Fuzzy.Parallel: data Scored a
+ Text.Fuzzy.Parallel: filter :: Int -> Int -> Text -> [t] -> (t -> Text) -> [Scored t]
+ Text.Fuzzy.Parallel: instance GHC.Base.Functor Text.Fuzzy.Parallel.Scored
+ Text.Fuzzy.Parallel: instance GHC.Show.Show (Text.Fuzzy.Parallel.SortState a)
+ Text.Fuzzy.Parallel: instance GHC.Show.Show a => GHC.Show.Show (Text.Fuzzy.Parallel.Scored a)
+ Text.Fuzzy.Parallel: match :: Text -> Text -> Maybe Int
+ Text.Fuzzy.Parallel: simpleFilter :: Int -> Int -> Text -> [Text] -> [Scored Text]
- Development.IDE: HiFileResult :: !ModSummary -> !HomeModInfo -> ByteString -> ByteString -> HiFileResult
+ Development.IDE: HiFileResult :: !ModSummary -> !HomeModInfo -> ByteString -> ByteString -> !ModuleEnv UTCTime -> HiFileResult
- Development.IDE: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> TcModuleResult
+ Development.IDE: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> !ModuleEnv UTCTime -> TcModuleResult
- Development.IDE: define :: IdeRule k v => (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()
+ Development.IDE: define :: IdeRule k v => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()
- Development.IDE: defineEarlyCutoff :: IdeRule k v => RuleBody k v -> Rules ()
+ Development.IDE: defineEarlyCutoff :: IdeRule k v => Recorder (WithPriority Log) -> RuleBody k v -> Rules ()
- Development.IDE: defineNoDiagnostics :: IdeRule k v => (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules ()
+ Development.IDE: defineNoDiagnostics :: IdeRule k v => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules ()
- Development.IDE: mkHiFileResult :: ModSummary -> HomeModInfo -> HiFileResult
+ Development.IDE: mkHiFileResult :: ModSummary -> HomeModInfo -> ModuleEnv UTCTime -> HiFileResult
- Development.IDE.Core.Compile: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> TcModuleResult
+ Development.IDE.Core.Compile: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> !ModuleEnv UTCTime -> TcModuleResult
- Development.IDE.Core.Compile: loadInterface :: (MonadIO m, MonadMask m) => HscEnv -> ModSummary -> SourceModified -> Maybe LinkableType -> (Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult)) -> m ([FileDiagnostic], Maybe HiFileResult)
+ Development.IDE.Core.Compile: loadInterface :: (MonadIO m, MonadMask m) => HscEnv -> ModSummary -> Maybe LinkableType -> RecompilationInfo m -> m ([FileDiagnostic], Maybe HiFileResult)
- Development.IDE.Core.Compile: typecheckModule :: IdeDefer -> HscEnv -> [Linkable] -> ParsedModule -> IO (IdeResult TcModuleResult)
+ Development.IDE.Core.Compile: typecheckModule :: IdeDefer -> HscEnv -> ModuleEnv UTCTime -> ParsedModule -> IO (IdeResult TcModuleResult)
- Development.IDE.Core.FileStore: fileStoreRules :: VFSHandle -> (NormalizedFilePath -> Action Bool) -> Rules ()
+ Development.IDE.Core.FileStore: fileStoreRules :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()
- Development.IDE.Core.FileStore: getFileContentsImpl :: VFSHandle -> NormalizedFilePath -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe Text))
+ Development.IDE.Core.FileStore: getFileContentsImpl :: NormalizedFilePath -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe Text))
- Development.IDE.Core.FileStore: getModificationTimeImpl :: VFSHandle -> Bool -> NormalizedFilePath -> Action (Maybe ByteString, ([FileDiagnostic], Maybe FileVersion))
+ Development.IDE.Core.FileStore: getModificationTimeImpl :: Bool -> NormalizedFilePath -> Action (Maybe ByteString, ([FileDiagnostic], Maybe FileVersion))
- Development.IDE.Core.FileStore: setFileModified :: IdeState -> Bool -> NormalizedFilePath -> IO ()
+ Development.IDE.Core.FileStore: setFileModified :: Recorder (WithPriority Log) -> VFSModified -> IdeState -> Bool -> NormalizedFilePath -> IO ()
- Development.IDE.Core.FileStore: setSomethingModified :: IdeState -> [Key] -> String -> IO ()
+ Development.IDE.Core.FileStore: setSomethingModified :: VFSModified -> IdeState -> [Key] -> String -> IO ()
- Development.IDE.Core.FileStore: typecheckParents :: IdeState -> NormalizedFilePath -> IO ()
+ Development.IDE.Core.FileStore: typecheckParents :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> IO ()
- Development.IDE.Core.OfInterest: ofInterestRules :: Rules ()
+ Development.IDE.Core.OfInterest: ofInterestRules :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.RuleTypes: HiFileResult :: !ModSummary -> !HomeModInfo -> ByteString -> ByteString -> HiFileResult
+ Development.IDE.Core.RuleTypes: HiFileResult :: !ModSummary -> !HomeModInfo -> ByteString -> ByteString -> !ModuleEnv UTCTime -> HiFileResult
- Development.IDE.Core.RuleTypes: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> TcModuleResult
+ Development.IDE.Core.RuleTypes: TcModuleResult :: ParsedModule -> RenamedSource -> TcGblEnv -> Splices -> !Bool -> !ModuleEnv UTCTime -> TcModuleResult
- Development.IDE.Core.RuleTypes: mkHiFileResult :: ModSummary -> HomeModInfo -> HiFileResult
+ Development.IDE.Core.RuleTypes: mkHiFileResult :: ModSummary -> HomeModInfo -> ModuleEnv UTCTime -> HiFileResult
- Development.IDE.Core.Rules: RulesConfig :: Bool -> Bool -> RulesConfig
+ Development.IDE.Core.Rules: RulesConfig :: Bool -> Bool -> LspT Config IO () -> RulesConfig
- Development.IDE.Core.Rules: defineEarlyCutOffNoFile :: IdeRule k v => (k -> Action (ByteString, v)) -> Rules ()
+ Development.IDE.Core.Rules: defineEarlyCutOffNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action (ByteString, v)) -> Rules ()
- Development.IDE.Core.Rules: defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()
+ Development.IDE.Core.Rules: defineNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules ()
- Development.IDE.Core.Rules: generateCoreRule :: Rules ()
+ Development.IDE.Core.Rules: generateCoreRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getBindingsRule :: Rules ()
+ Development.IDE.Core.Rules: getBindingsRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getClientSettingsRule :: Rules ()
+ Development.IDE.Core.Rules: getClientSettingsRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getDependencyInformationRule :: Rules ()
+ Development.IDE.Core.Rules: getDependencyInformationRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getDocMapRule :: Rules ()
+ Development.IDE.Core.Rules: getDocMapRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getHieAstsRule :: Rules ()
+ Development.IDE.Core.Rules: getHieAstsRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getImportMapRule :: Rules ()
+ Development.IDE.Core.Rules: getImportMapRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getLocatedImportsRule :: Rules ()
+ Development.IDE.Core.Rules: getLocatedImportsRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getModIfaceFromDiskRule :: Rules ()
+ Development.IDE.Core.Rules: getModIfaceFromDiskRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getModIfaceRule :: Rules ()
+ Development.IDE.Core.Rules: getModIfaceRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getModSummaryRule :: Rules ()
+ Development.IDE.Core.Rules: getModSummaryRule :: LspT Config IO () -> Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getModuleGraphRule :: Rules ()
+ Development.IDE.Core.Rules: getModuleGraphRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getParsedModuleRule :: Rules ()
+ Development.IDE.Core.Rules: getParsedModuleRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: getParsedModuleWithCommentsRule :: Rules ()
+ Development.IDE.Core.Rules: getParsedModuleWithCommentsRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: knownFilesRule :: Rules ()
+ Development.IDE.Core.Rules: knownFilesRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: loadGhcSession :: GhcSessionDepsConfig -> Rules ()
+ Development.IDE.Core.Rules: loadGhcSession :: Recorder (WithPriority Log) -> GhcSessionDepsConfig -> Rules ()
- Development.IDE.Core.Rules: mainRule :: RulesConfig -> Rules ()
+ Development.IDE.Core.Rules: mainRule :: Recorder (WithPriority Log) -> RulesConfig -> Rules ()
- Development.IDE.Core.Rules: reportImportCyclesRule :: Rules ()
+ Development.IDE.Core.Rules: reportImportCyclesRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Rules: typeCheckRule :: Rules ()
+ Development.IDE.Core.Rules: typeCheckRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Core.Service: initialise :: Config -> Rules () -> Maybe (LanguageContextEnv Config) -> Logger -> Debouncer NormalizedUri -> IdeOptions -> VFSHandle -> WithHieDb -> IndexQueue -> IO IdeState
+ Development.IDE.Core.Service: initialise :: Recorder (WithPriority Log) -> Config -> Rules () -> Maybe (LanguageContextEnv Config) -> Logger -> Debouncer NormalizedUri -> IdeOptions -> WithHieDb -> IndexQueue -> IO IdeState
- Development.IDE.Core.Shake: ShakeExtras :: Maybe (LanguageContextEnv Config) -> Debouncer NormalizedUri -> Logger -> TVar (HashMap TypeRep Dynamic) -> Values -> STMDiagnosticStore -> STMDiagnosticStore -> Map NormalizedUri [Diagnostic] -> Map NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping)) -> ProgressReporting -> IdeTesting -> (String -> [DelayedAction ()] -> IO ()) -> IORef NameCache -> TVar (Hashed KnownTargets) -> TVar ExportsMap -> ActionQueue -> ClientCapabilities -> WithHieDb -> HieDbWriter -> TVar (HashMap Key GetStalePersistent) -> VFSHandle -> Config -> TVar (HashSet Key) -> ShakeExtras
+ Development.IDE.Core.Shake: ShakeExtras :: Maybe (LanguageContextEnv Config) -> Debouncer NormalizedUri -> Logger -> TVar (HashMap TypeRep Dynamic) -> Values -> STMDiagnosticStore -> STMDiagnosticStore -> Map NormalizedUri [Diagnostic] -> Map NormalizedUri (EnumMap Int32 (PositionDelta, PositionMapping)) -> ProgressReporting -> IdeTesting -> (VFSModified -> String -> [DelayedAction ()] -> IO ()) -> IORef NameCache -> TVar (Hashed KnownTargets) -> TVar ExportsMap -> ActionQueue -> ClientCapabilities -> WithHieDb -> HieDbWriter -> TVar (HashMap Key GetStalePersistent) -> TVar VFS -> Config -> TVar (HashSet Key) -> ShakeExtras
- Development.IDE.Core.Shake: [$sel:positionMapping:ShakeExtras] :: ShakeExtras -> Map NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping))
+ Development.IDE.Core.Shake: [$sel:positionMapping:ShakeExtras] :: ShakeExtras -> Map NormalizedUri (EnumMap Int32 (PositionDelta, PositionMapping))
- Development.IDE.Core.Shake: [$sel:restartShakeSession:ShakeExtras] :: ShakeExtras -> String -> [DelayedAction ()] -> IO ()
+ Development.IDE.Core.Shake: [$sel:restartShakeSession:ShakeExtras] :: ShakeExtras -> VFSModified -> String -> [DelayedAction ()] -> IO ()
- Development.IDE.Core.Shake: define :: IdeRule k v => (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()
+ Development.IDE.Core.Shake: define :: IdeRule k v => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()
- Development.IDE.Core.Shake: defineEarlyCutOffNoFile :: IdeRule k v => (k -> Action (ByteString, v)) -> Rules ()
+ Development.IDE.Core.Shake: defineEarlyCutOffNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action (ByteString, v)) -> Rules ()
- Development.IDE.Core.Shake: defineEarlyCutoff :: IdeRule k v => RuleBody k v -> Rules ()
+ Development.IDE.Core.Shake: defineEarlyCutoff :: IdeRule k v => Recorder (WithPriority Log) -> RuleBody k v -> Rules ()
- Development.IDE.Core.Shake: defineNoDiagnostics :: IdeRule k v => (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules ()
+ Development.IDE.Core.Shake: defineNoDiagnostics :: IdeRule k v => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules ()
- Development.IDE.Core.Shake: defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()
+ Development.IDE.Core.Shake: defineNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules ()
- Development.IDE.Core.Shake: newSession :: ShakeExtras -> ShakeDatabase -> [DelayedActionInternal] -> String -> IO ShakeSession
+ Development.IDE.Core.Shake: newSession :: Recorder (WithPriority Log) -> ShakeExtras -> VFSModified -> ShakeDatabase -> [DelayedActionInternal] -> String -> IO ShakeSession
- Development.IDE.Core.Shake: shakeOpen :: Maybe (LanguageContextEnv Config) -> Config -> Logger -> Debouncer NormalizedUri -> Maybe FilePath -> IdeReportProgress -> IdeTesting -> WithHieDb -> IndexQueue -> VFSHandle -> ShakeOptions -> Rules () -> IO IdeState
+ Development.IDE.Core.Shake: shakeOpen :: Recorder (WithPriority Log) -> Maybe (LanguageContextEnv Config) -> Config -> Logger -> Debouncer NormalizedUri -> Maybe FilePath -> IdeReportProgress -> IdeTesting -> WithHieDb -> IndexQueue -> ShakeOptions -> Rules () -> IO IdeState
- Development.IDE.Core.Shake: shakeSessionInit :: IdeState -> IO ()
+ Development.IDE.Core.Shake: shakeSessionInit :: Recorder (WithPriority Log) -> IdeState -> IO ()
- Development.IDE.GHC.ExactPrint: getAnnotatedParsedSourceRule :: Rules ()
+ Development.IDE.GHC.ExactPrint: getAnnotatedParsedSourceRule :: Recorder (WithPriority Log) -> Rules ()
- Development.IDE.Import.DependencyInformation: RawDependencyInformation :: !FilePathIdMap (Either ModuleParseError ModuleImports) -> !PathIdMap -> !BootIdMap -> RawDependencyInformation
+ Development.IDE.Import.DependencyInformation: RawDependencyInformation :: !FilePathIdMap (Either ModuleParseError ModuleImports) -> !PathIdMap -> !BootIdMap -> !FilePathIdMap ShowableModuleName -> RawDependencyInformation
- Development.IDE.LSP.LanguageServer: runLanguageServer :: forall config. Show config => Options -> Handle -> Handle -> (FilePath -> IO FilePath) -> config -> (config -> Value -> Either Text config) -> Handlers (ServerM config) -> (LanguageContextEnv config -> VFSHandle -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState) -> IO ()
+ Development.IDE.LSP.LanguageServer: runLanguageServer :: forall config. Show config => Recorder (WithPriority Log) -> Options -> Handle -> Handle -> (FilePath -> IO FilePath) -> config -> (config -> Value -> Either Text config) -> Handlers (ServerM config) -> (LanguageContextEnv config -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState) -> IO ()
- 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: notificationHandler :: forall (m :: Method FromClient Notification) c. HasTracing (MessageParams m) => SMethod m -> (IdeState -> VFS -> MessageParams m -> LspM c ()) -> Handlers (ServerM c)
- Development.IDE.Main: defaultArguments :: Priority -> Arguments
+ Development.IDE.Main: defaultArguments :: Recorder (WithPriority Log) -> Logger -> Arguments
- Development.IDE.Main: defaultMain :: Arguments -> IO ()
+ Development.IDE.Main: defaultMain :: Recorder (WithPriority Log) -> Arguments -> IO ()
- Development.IDE.Main: testing :: Arguments
+ Development.IDE.Main: testing :: Recorder (WithPriority Log) -> Logger -> Arguments
- Development.IDE.Main.HeapStats: withHeapStats :: Logger -> IO r -> IO r
+ Development.IDE.Main.HeapStats: withHeapStats :: Recorder (WithPriority Log) -> IO r -> IO r
- Development.IDE.Plugin.Completions: descriptor :: PluginId -> PluginDescriptor IdeState
+ Development.IDE.Plugin.Completions: descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
- Development.IDE.Plugin.HLS: asGhcIdePlugin :: IdePlugins IdeState -> Plugin Config
+ Development.IDE.Plugin.HLS: asGhcIdePlugin :: Recorder (WithPriority Log) -> IdePlugins IdeState -> Plugin Config
- Development.IDE.Plugin.HLS.GhcIde: descriptors :: [PluginDescriptor IdeState]
+ Development.IDE.Plugin.HLS.GhcIde: descriptors :: Recorder (WithPriority Log) -> [PluginDescriptor IdeState]
- Development.IDE.Plugin.TypeLenses: descriptor :: PluginId -> PluginDescriptor IdeState
+ Development.IDE.Plugin.TypeLenses: descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
- Development.IDE.Session: SessionLoadingOptions :: (FilePath -> IO (Maybe FilePath)) -> (Maybe FilePath -> FilePath -> IO (Cradle Void)) -> (String -> [String] -> IO CacheDirs) -> (Logger -> FilePath -> IO (Maybe LibDir)) -> UnitId -> SessionLoadingOptions
+ Development.IDE.Session: SessionLoadingOptions :: (FilePath -> IO (Maybe FilePath)) -> (Maybe FilePath -> FilePath -> IO (Cradle Void)) -> (String -> [String] -> IO CacheDirs) -> (Recorder (WithPriority Log) -> FilePath -> IO (Maybe LibDir)) -> UnitId -> SessionLoadingOptions
- Development.IDE.Session: [getInitialGhcLibDir] :: SessionLoadingOptions -> Logger -> FilePath -> IO (Maybe LibDir)
+ Development.IDE.Session: [getInitialGhcLibDir] :: SessionLoadingOptions -> Recorder (WithPriority Log) -> FilePath -> IO (Maybe LibDir)
- Development.IDE.Session: loadSession :: FilePath -> IO (Action IdeGhcSession)
+ Development.IDE.Session: loadSession :: Recorder (WithPriority Log) -> FilePath -> IO (Action IdeGhcSession)
- Development.IDE.Session: loadSessionWithOptions :: SessionLoadingOptions -> FilePath -> IO (Action IdeGhcSession)
+ Development.IDE.Session: loadSessionWithOptions :: Recorder (WithPriority Log) -> SessionLoadingOptions -> FilePath -> IO (Action IdeGhcSession)
- Development.IDE.Session: retryOnException :: (MonadIO m, MonadCatch m, RandomGen g, Exception e) => (e -> Maybe e) -> Logger -> Int -> Int -> Int -> g -> m a -> m a
+ Development.IDE.Session: retryOnException :: (MonadIO m, MonadCatch m, RandomGen g, Exception e) => (e -> Maybe e) -> Recorder (WithPriority Log) -> Int -> Int -> Int -> g -> m a -> m a
- Development.IDE.Session: retryOnSqliteBusy :: (MonadIO m, MonadCatch m, RandomGen g) => Logger -> g -> m a -> m a
+ Development.IDE.Session: retryOnSqliteBusy :: (MonadIO m, MonadCatch m, RandomGen g) => Recorder (WithPriority Log) -> g -> m a -> m a
- Development.IDE.Session: runWithDb :: Logger -> FilePath -> (WithHieDb -> IndexQueue -> IO ()) -> IO ()
+ Development.IDE.Session: runWithDb :: Recorder (WithPriority Log) -> FilePath -> (WithHieDb -> IndexQueue -> IO ()) -> IO ()
- Development.IDE.Session: setInitialDynFlags :: Logger -> FilePath -> SessionLoadingOptions -> IO (Maybe LibDir)
+ Development.IDE.Session: setInitialDynFlags :: Recorder (WithPriority Log) -> FilePath -> SessionLoadingOptions -> IO (Maybe LibDir)
- Development.IDE.Types.Exports: ExportsMap :: HashMap IdentifierText (HashSet IdentInfo) -> HashMap ModuleNameText (HashSet IdentInfo) -> ExportsMap
+ Development.IDE.Types.Exports: ExportsMap :: !HashMap IdentifierText (HashSet IdentInfo) -> !HashMap ModuleNameText (HashSet IdentInfo) -> ExportsMap
- Development.IDE.Types.Exports: [getExportsMap] :: ExportsMap -> HashMap IdentifierText (HashSet IdentInfo)
+ Development.IDE.Types.Exports: [getExportsMap] :: ExportsMap -> !HashMap IdentifierText (HashSet IdentInfo)
- Development.IDE.Types.Exports: [getModuleExportsMap] :: ExportsMap -> HashMap ModuleNameText (HashSet IdentInfo)
+ Development.IDE.Types.Exports: [getModuleExportsMap] :: ExportsMap -> !HashMap ModuleNameText (HashSet IdentInfo)
- Development.IDE.Types.Shake: Stale :: Maybe PositionDelta -> TextDocumentVersion -> v -> Value v
+ Development.IDE.Types.Shake: Stale :: Maybe PositionDelta -> Maybe FileVersion -> v -> Value v
- Development.IDE.Types.Shake: Succeeded :: TextDocumentVersion -> v -> Value v
+ Development.IDE.Types.Shake: Succeeded :: Maybe FileVersion -> v -> Value v

Files

bench/hist/Main.hs view
@@ -57,7 +57,6 @@ import           System.Console.GetOpt import           System.FilePath - configPath :: FilePath configPath = "bench/config.yaml" @@ -88,6 +87,8 @@ ghcideBuildRules = MkBuildRules findGhcForBuildSystem "ghcide" projectDepends buildGhcide   where       projectDepends = do+        need . map ("../hls-graph/src" </>) =<< getDirectoryFiles "../hls-graph/src" ["//*.hs"]+        need . map ("../hls-plugin-api/src" </>) =<< getDirectoryFiles "../hls-plugin-api/src" ["//*.hs"]         need . map ("src" </>) =<< getDirectoryFiles "src" ["//*.hs"]         need . map ("session-loader" </>) =<< getDirectoryFiles "session-loader" ["//*.hs"]         need =<< getDirectoryFiles "." ["*.cabal"]
bench/lib/Experiments.hs view
@@ -37,7 +37,9 @@                                                   getBuildKeysBuilt,                                                   getBuildKeysChanged,                                                   getBuildKeysVisited,-                                                  getStoredKeys)+                                                  getStoredKeys,+                                                  getRebuildsCount,+                                                  ) import           Development.IDE.Test.Diagnostic import           Development.Shake               (CmdOption (Cwd, FileStdout),                                                   cmd_)@@ -329,12 +331,15 @@         , "setup"         , "userTime"         , "delayedTime"+        , "firstBuildTime"+        , "averageTimePerResponse"         , "totalTime"         , "buildRulesBuilt"         , "buildRulesChanged"         , "buildRulesVisited"         , "buildRulesTotal"         , "buildEdges"+        , "ghcRebuilds"         ]       rows =         [ [ name,@@ -344,15 +349,21 @@             show runSetup',             show userWaits,             show delayedWork,+            show $ firstResponse+firstResponseDelayed,+            -- Exclude first response as it has a lot of setup time included+            -- Assume that number of requests = number of modules * number of samples+            show ((userWaits - firstResponse)/((fromIntegral samples - 1)*modules)),             show runExperiment,             show rulesBuilt,             show rulesChanged,             show rulesVisited,             show rulesTotal,-            show edgesTotal+            show edgesTotal,+            show rebuildsTotal           ]           | (Bench {name, samples}, BenchRun {..}) <- results,             let runSetup' = if runSetup < 0.01 then 0 else runSetup+                modules = fromIntegral $ length $ exampleModules $ example ?config         ]       csv = unlines $ map (intercalate ", ") (headers : rows)   writeFile (outputCSV ?config) csv@@ -369,12 +380,14 @@             showDuration runSetup',             showDuration userWaits,             showDuration delayedWork,+            showDuration firstResponse,             showDuration runExperiment,             show rulesBuilt,             show rulesChanged,             show rulesVisited,             show rulesTotal,-            show edgesTotal+            show edgesTotal,+            show rebuildsTotal           ]           | (Bench {name, samples}, BenchRun {..}) <- results,             let runSetup' = if runSetup < 0.01 then 0 else runSetup@@ -420,16 +433,19 @@     runExperiment :: !Seconds,     userWaits     :: !Seconds,     delayedWork   :: !Seconds,+    firstResponse :: !Seconds,+    firstResponseDelayed :: !Seconds,     rulesBuilt    :: !Int,     rulesChanged  :: !Int,     rulesVisited  :: !Int,     rulesTotal    :: !Int,     edgesTotal    :: !Int,+    rebuildsTotal :: !Int,     success       :: !Bool   }  badRun :: BenchRun-badRun = BenchRun 0 0 0 0 0 0 0 0 0 0 False+badRun = BenchRun 0 0 0 0 0 0 0 0 0 0 0 0 0 False  waitForProgressStart :: Session () waitForProgressStart = void $ do@@ -482,8 +498,8 @@        liftIO $ output $ "Running " <> name <> " benchmark"       (runSetup, ()) <- duration $ benchSetup docs-      let loop !userWaits !delayedWork 0 = return $ Just (userWaits, delayedWork)-          loop !userWaits !delayedWork n = do+      let loop' (Just timeForFirstResponse) !userWaits !delayedWork 0 = return $ Just (userWaits, delayedWork, timeForFirstResponse)+          loop' timeForFirstResponse !userWaits !delayedWork n = do             (t, res) <- duration $ experiment docs             if not res               then return Nothing@@ -491,17 +507,19 @@                 output (showDuration t)                 -- Wait for the delayed actions to finish                 td <- waitForBuildQueue-                loop (userWaits+t) (delayedWork+td) (n -1)+                loop' (timeForFirstResponse <|> (Just (t,td))) (userWaits+t) (delayedWork+td) (n -1)+          loop = loop' Nothing        (runExperiment, result) <- duration $ loop 0 0 samples       let success = isJust result-          (userWaits, delayedWork) = fromMaybe (0,0) result+          (userWaits, delayedWork, (firstResponse, firstResponseDelayed)) = fromMaybe (0,0,(0,0)) result        rulesTotal <- length <$> getStoredKeys       rulesBuilt <- either (const 0) length <$> getBuildKeysBuilt       rulesChanged <- either (const 0) length <$> getBuildKeysChanged       rulesVisited <- either (const 0) length <$> getBuildKeysVisited       edgesTotal   <- fromRight 0 <$> getBuildEdgesCount+      rebuildsTotal <- fromRight 0 <$> getRebuildsCount        return BenchRun {..} 
exe/Main.hs view
@@ -8,20 +8,34 @@ import           Arguments                         (Arguments (..),                                                     getArguments) import           Control.Monad.Extra               (unless)+import           Control.Monad.IO.Class            (liftIO) import           Data.Default                      (def)+import           Data.Function                     ((&)) import           Data.Version                      (showVersion) import           Development.GitRev                (gitHash)-import           Development.IDE                   (Priority (Debug, Info),-                                                    action)+import           Development.IDE                   (action) import           Development.IDE.Core.OfInterest   (kick) import           Development.IDE.Core.Rules        (mainRule)+import qualified Development.IDE.Core.Rules        as Rules import           Development.IDE.Core.Tracing      (withTelemetryLogger)-import           Development.IDE.Graph             (ShakeOptions (shakeThreads))-import qualified Development.IDE.Main              as Main+import qualified Development.IDE.Main              as IDEMain import qualified Development.IDE.Plugin.HLS.GhcIde as GhcIde+import           Development.IDE.Types.Logger      (Logger (Logger),+                                                    LoggingColumn (DataColumn, PriorityColumn),+                                                    Pretty (pretty),+                                                    Priority (Debug, Info, Error),+                                                    Recorder (Recorder),+                                                    WithPriority (WithPriority, priority),+                                                    cfilter, cmapWithPrio,+                                                    makeDefaultStderrRecorder, layoutPretty, renderStrict, defaultLayoutOptions)+import qualified Development.IDE.Types.Logger      as Logger import           Development.IDE.Types.Options+import           GHC.Stack                         (emptyCallStack)+import           Language.LSP.Server               as LSP+import           Language.LSP.Types                as LSP import           Ide.Plugin.Config                 (Config (checkParents, checkProject)) import           Ide.PluginUtils                   (pluginDescToIdePlugins)+import           Ide.Types                         (PluginDescriptor (pluginNotificationHandlers), defaultPluginDescriptor, mkPluginNotificationHandler) import           Paths_ghcide                      (version) import qualified System.Directory.Extra            as IO import           System.Environment                (getExecutablePath)@@ -29,6 +43,17 @@ import           System.IO                         (hPutStrLn, stderr) import           System.Info                       (compilerVersion) +data Log+  = LogIDEMain IDEMain.Log+  | LogRules Rules.Log+  | LogGhcIde GhcIde.Log++instance Pretty Log where+  pretty = \case+    LogIDEMain log -> pretty log+    LogRules log   -> pretty log+    LogGhcIde log  -> pretty log+ ghcideVersion :: IO String ghcideVersion = do   path <- getExecutablePath@@ -42,7 +67,12 @@  main :: IO () main = withTelemetryLogger $ \telemetryLogger -> do-    let hlsPlugins = pluginDescToIdePlugins GhcIde.descriptors+    -- stderr recorder just for plugin cli commands+    pluginCliRecorder <-+      cmapWithPrio pretty+      <$> makeDefaultStderrRecorder (Just [PriorityColumn, DataColumn]) Info++    let hlsPlugins = pluginDescToIdePlugins (GhcIde.descriptors (cmapWithPrio LogGhcIde pluginCliRecorder))     -- WARNING: If you write to stdout before runLanguageServer     --          then the language server will not work     Arguments{..} <- getArguments hlsPlugins@@ -55,30 +85,59 @@       Nothing   -> IO.getCurrentDirectory       Just root -> IO.setCurrentDirectory root >> IO.getCurrentDirectory -    let logPriority = if argsVerbose then Debug else Info-        arguments = if argsTesting then Main.testing else Main.defaultArguments logPriority+    let minPriority = if argsVerbose then Debug else Info -    Main.defaultMain arguments-        { Main.argsProjectRoot = Just argsCwd-        , Main.argCommand = argsCommand-        ,Main.argsLogger = Main.argsLogger arguments <> pure telemetryLogger+    docWithPriorityRecorder <- makeDefaultStderrRecorder (Just [PriorityColumn, DataColumn]) minPriority -        ,Main.argsRules = do+    (lspLogRecorder, cb1) <- Logger.withBacklog Logger.lspClientLogRecorder+    (lspMessageRecorder, cb2) <- Logger.withBacklog Logger.lspClientMessageRecorder+    -- This plugin just installs a handler for the `initialized` notification, which then+    -- picks up the LSP environment and feeds it to our recorders+    let lspRecorderPlugin = (defaultPluginDescriptor "LSPRecorderCallback")+          { pluginNotificationHandlers = mkPluginNotificationHandler LSP.SInitialized $ \_ _ _ _ -> do+              env <- LSP.getLspEnv+              liftIO $ (cb1 <> cb2) env+          }++    let docWithFilteredPriorityRecorder =+          (docWithPriorityRecorder & cfilter (\WithPriority{ priority } -> priority >= minPriority)) <>+          (lspLogRecorder & cmapWithPrio (renderStrict . layoutPretty defaultLayoutOptions)+                          & cfilter (\WithPriority{ priority } -> priority >= minPriority)) <>+          (lspMessageRecorder & cmapWithPrio (renderStrict . layoutPretty defaultLayoutOptions)+                              & cfilter (\WithPriority{ priority } -> priority >= Error))++    -- exists so old-style logging works. intended to be phased out+    let logger = Logger $ \p m -> Logger.logger_ docWithFilteredPriorityRecorder (WithPriority p emptyCallStack (pretty m))++    let recorder = docWithFilteredPriorityRecorder+                 & cmapWithPrio pretty++    let arguments =+          if argsTesting+          then IDEMain.testing (cmapWithPrio LogIDEMain recorder) logger+          else IDEMain.defaultArguments (cmapWithPrio LogIDEMain recorder) logger++    IDEMain.defaultMain (cmapWithPrio LogIDEMain recorder) arguments+        { IDEMain.argsProjectRoot = Just argsCwd+        , IDEMain.argCommand = argsCommand+        , IDEMain.argsLogger = IDEMain.argsLogger arguments <> pure telemetryLogger+        , IDEMain.argsHlsPlugins = IDEMain.argsHlsPlugins arguments <> pluginDescToIdePlugins [lspRecorderPlugin]++        , IDEMain.argsRules = do             -- install the main and ghcide-plugin rules-            mainRule def+            mainRule (cmapWithPrio LogRules recorder) def             -- install the kick action, which triggers a typecheck on every             -- Shake database restart, i.e. on every user edit.             unless argsDisableKick $                 action kick -        ,Main.argsThreads = case argsThreads of 0 -> Nothing ; i -> Just (fromIntegral i)+        , IDEMain.argsThreads = case argsThreads of 0 -> Nothing ; i -> Just (fromIntegral i) -        ,Main.argsIdeOptions = \config sessionLoader ->-            let defOptions = Main.argsIdeOptions arguments config sessionLoader+        , IDEMain.argsIdeOptions = \config sessionLoader ->+            let defOptions = IDEMain.argsIdeOptions arguments config sessionLoader             in defOptions                 { optShakeProfiling = argsShakeProfiling                 , optOTMemoryProfiling = IdeOTMemoryProfiling argsOTMemoryProfiling-                , optShakeOptions = (optShakeOptions defOptions){shakeThreads = argsThreads}                 , optCheckParents = pure $ checkParents config                 , optCheckProject = pure $ checkProject config                 , optRunSubset = not argsConservativeChangeTracking
ghcide.cabal view
@@ -2,7 +2,7 @@ build-type:         Simple category:           Development name:               ghcide-version:            1.6.0.1+version:            1.7.0.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/haskell-language-server/tree/master/ghcide#readme bug-reports:        https://github.com/haskell/haskell-language-server/issues-tested-with:        GHC == 8.6.5 || == 8.8.4 || == 8.10.6 || == 8.10.7 || == 9.0.1 || == 9.0.2 || == 9.2.1+tested-with:        GHC == 8.6.5 || == 8.8.4 || == 8.10.6 || == 8.10.7 || == 9.0.1 || == 9.0.2 || == 9.2.1 || == 9.2.2 extra-source-files: README.md CHANGELOG.md                     test/data/**/*.project                     test/data/**/*.cabal@@ -50,7 +50,7 @@         dlist,         exceptions,         extra >= 1.7.4,-        fuzzy,+        enummapset,         filepath,         fingertree,         focus,@@ -60,7 +60,7 @@         haddock-library >= 1.8 && < 1.11,         hashable,         hie-compat ^>= 0.2.0.0,-        hls-plugin-api ^>= 1.2.0.2 || ^>= 1.3,+        hls-plugin-api ^>= 1.4,         lens,         list-t,         hiedb == 0.4.1.*,@@ -79,7 +79,7 @@         rope-utf16-splay,         safe,         safe-exceptions,-        hls-graph ^>= 1.6,+        hls-graph ^>= 1.7,         sorted-list,         sqlite-simple,         stm,@@ -105,7 +105,7 @@         ghc-check >=0.5.0.4,         ghc-paths,         cryptohash-sha1 >=0.11.100 && <0.12,-        hie-bios >= 0.8 && < 0.9.0,+        hie-bios ^>= 0.9.1,         implicit-hie-cradle ^>= 0.3.0.5 || ^>= 0.5,         base16-bytestring >=0.1.1 && <1.1     if os(windows)@@ -148,6 +148,7 @@         Development.IDE.Main.HeapStats         Development.IDE.Core.Debouncer         Development.IDE.Core.FileStore+        Development.IDE.Core.FileUtils         Development.IDE.Core.IdeConfiguration         Development.IDE.Core.OfInterest         Development.IDE.Core.PositionMapping@@ -205,6 +206,7 @@         Development.IDE.Plugin.HLS.GhcIde         Development.IDE.Plugin.Test         Development.IDE.Plugin.TypeLenses+        Text.Fuzzy.Parallel      other-modules:         Development.IDE.Core.FileExists@@ -216,7 +218,6 @@         Development.IDE.Plugin.Completions.Logic         Development.IDE.Session.VersionCheck         Development.IDE.Types.Action-        Text.Fuzzy.Parallel      ghc-options:                 -Wall@@ -371,6 +372,7 @@         directory,         extra,         filepath,+        fuzzy,         --------------------------------------------------------------         -- The MIN_VERSION_ghc macro relies on MIN_VERSION pragmas         -- which require depending on ghc. So the tests need to depend@@ -385,11 +387,13 @@         lsp,         lsp-types,         hls-plugin-api,-        network-uri,         lens,         list-t,         lsp-test ^>= 0.14,+        monoid-subclasses,+        network-uri,         optparse-applicative,+        parallel,         process,         QuickCheck,         quickcheck-instances,@@ -410,6 +414,7 @@         tasty-rerun,         text,         unordered-containers,+        vector,     if (impl(ghc >= 8.6) && impl(ghc < 9.2))       build-depends:           record-dot-preprocessor,@@ -423,6 +428,7 @@         Development.IDE.Test.Runfiles         Experiments         Experiments.Types+        FuzzySearch         Progress         HieDbRetry     default-extensions:
session-loader/Development/IDE/Session.hs view
@@ -1,5 +1,6 @@-{-# LANGUAGE RankNTypes   #-}-{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE RankNTypes                #-}+{-# LANGUAGE TypeFamilies              #-}  {-| The logic for setting up a ghcide session by tapping into hie-bios.@@ -14,6 +15,7 @@   ,runWithDb   ,retryOnSqliteBusy   ,retryOnException+  ,Log(..)   ) where  -- Unfortunately, we cannot use loadSession with ghc-lib since hie-bios uses@@ -27,7 +29,7 @@ import           Control.Monad.Extra import           Control.Monad.IO.Class import qualified Crypto.Hash.SHA1                     as H-import           Data.Aeson+import           Data.Aeson                           hiding (Error) import           Data.Bifunctor import qualified Data.ByteString.Base16               as B16 import qualified Data.ByteString.Char8                as B@@ -44,11 +46,12 @@ import           Data.Time.Clock import           Data.Version import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Shake           hiding (withHieDb)+import           Development.IDE.Core.Shake           hiding (Log, Priority,+                                                       withHieDb) import qualified Development.IDE.GHC.Compat           as Compat import           Development.IDE.GHC.Compat.Core      hiding (Target,                                                        TargetFile, TargetModule,-                                                       Var)+                                                       Var, Warning) import qualified Development.IDE.GHC.Compat.Core      as GHC import           Development.IDE.GHC.Compat.Env       hiding (Logger) import           Development.IDE.GHC.Compat.Units     (UnitId)@@ -60,7 +63,11 @@ import           Development.IDE.Types.HscEnvEq       (HscEnvEq, newHscEnvEq,                                                        newHscEnvEqPreserveImportPaths) import           Development.IDE.Types.Location-import           Development.IDE.Types.Logger+import           Development.IDE.Types.Logger         (Pretty (pretty),+                                                       Priority (Debug, Error, Info, Warning),+                                                       Recorder, WithPriority,+                                                       logWith, nest, vcat,+                                                       viaShow, (<+>)) import           Development.IDE.Types.Options import           GHC.Check import qualified HIE.Bios                             as HieBios@@ -72,7 +79,6 @@ import           System.Directory import qualified System.Directory.Extra               as IO import           System.FilePath-import           System.IO import           System.Info  import           Control.Applicative                  (Alternative ((<|>)))@@ -82,6 +88,8 @@                                                        readTVar, writeTVar) import           Control.Concurrent.STM.TQueue import           Data.Foldable                        (for_)+import           Data.HashMap.Strict                  (HashMap)+import           Data.HashSet                         (HashSet) import qualified Data.HashSet                         as Set import           Database.SQLite.Simple import           Development.IDE.Core.Tracing         (withTrace)@@ -91,7 +99,96 @@ import           HieDb.Utils import           System.Random                        (RandomGen) import qualified System.Random                        as Random+import Control.Monad.IO.Unlift (MonadUnliftIO) +data Log+  = LogSettingInitialDynFlags+  | LogGetInitialGhcLibDirDefaultCradleFail !CradleError !FilePath !(Maybe FilePath) !(Cradle Void)+  | LogGetInitialGhcLibDirDefaultCradleNone+  | LogHieDbRetry !Int !Int !Int !SomeException+  | LogHieDbRetriesExhausted !Int !Int !Int !SomeException+  | LogHieDbWriterThreadSQLiteError !SQLError+  | LogHieDbWriterThreadException !SomeException+  | LogInterfaceFilesCacheDir !FilePath+  | LogKnownFilesUpdated !(HashMap Target (HashSet NormalizedFilePath))+  | LogMakingNewHscEnv ![UnitId]+  | LogDLLLoadError !String+  | LogCradlePath !FilePath+  | LogCradleNotFound !FilePath+  | LogSessionLoadingResult !(Either [CradleError] (ComponentOptions, FilePath))+  | LogCradle !(Cradle Void)+  | LogNoneCradleFound FilePath+  | LogNewComponentCache !(([FileDiagnostic], Maybe HscEnvEq), DependencyInfo)+deriving instance Show Log++instance Pretty Log where+  pretty = \case+    LogNoneCradleFound path ->+      "None cradle found for" <+> pretty path <+> ", ignoring the file"+    LogSettingInitialDynFlags ->+      "Setting initial dynflags..."+    LogGetInitialGhcLibDirDefaultCradleFail cradleError rootDirPath hieYamlPath cradle ->+      nest 2 $+        vcat+          [ "Couldn't load cradle for ghc libdir."+          , "Cradle error:" <+> viaShow cradleError+          , "Root dir path:" <+> pretty rootDirPath+          , "hie.yaml path:" <+> pretty hieYamlPath+          , "Cradle:" <+> viaShow cradle ]+    LogGetInitialGhcLibDirDefaultCradleNone ->+      "Couldn't load cradle. Cradle not found."+    LogHieDbRetry delay maxDelay maxRetryCount e ->+      nest 2 $+        vcat+          [ "Retrying hiedb action..."+          , "delay:" <+> pretty delay+          , "maximum delay:" <+> pretty maxDelay+          , "retries remaining:" <+> pretty maxRetryCount+          , "SQLite error:" <+> pretty (displayException e) ]+    LogHieDbRetriesExhausted baseDelay maxDelay maxRetryCount e ->+      nest 2 $+        vcat+          [ "Retries exhausted for hiedb action."+          , "base delay:" <+> pretty baseDelay+          , "maximum delay:" <+> pretty maxDelay+          , "retries remaining:" <+> pretty maxRetryCount+          , "Exception:" <+> pretty (displayException e) ]+    LogHieDbWriterThreadSQLiteError e ->+      nest 2 $+        vcat+          [ "HieDb writer thread SQLite error:"+          , pretty (displayException e) ]+    LogHieDbWriterThreadException e ->+      nest 2 $+        vcat+          [ "HieDb writer thread exception:"+          , pretty (displayException e) ]+    LogInterfaceFilesCacheDir path ->+      "Interface files cache directory:" <+> pretty path+    LogKnownFilesUpdated targetToPathsMap ->+      nest 2 $+        vcat+          [ "Known files updated:"+          , viaShow $ (HM.map . Set.map) fromNormalizedFilePath targetToPathsMap+          ]+    LogMakingNewHscEnv inPlaceUnitIds ->+      "Making new HscEnv. In-place unit ids:" <+> pretty (map show inPlaceUnitIds)+    LogDLLLoadError errorString ->+      "Error dynamically loading libm.so.6:" <+> pretty errorString+    LogCradlePath path ->+      "Cradle path:" <+> pretty path+    LogCradleNotFound path ->+      vcat+        [ "No [cradle](https://github.com/mpickering/hie-bios#hie-bios) found for" <+> pretty path <> "."+        , "Proceeding with [implicit cradle](https://hackage.haskell.org/package/implicit-hie)."+        , "You should ignore this message, unless you see a 'Multi Cradle: No prefixes matched' error." ]+    LogSessionLoadingResult e ->+      "Session loading result:" <+> viaShow e+    LogCradle cradle ->+      "Cradle:" <+> viaShow cradle+    LogNewComponentCache componentCache ->+      "New component cache HscEnvEq:" <+> viaShow componentCache+ -- | Bump this version number when making changes to the format of the data stored in hiedb hiedbDataVersion :: String hiedbDataVersion = "1"@@ -110,7 +207,7 @@   --   or 'Nothing' to respect the cradle setting   , getCacheDirs           :: String -> [String] -> IO CacheDirs   -- | Return the GHC lib dir to use for the 'unsafeGlobalDynFlags'-  , getInitialGhcLibDir    :: Logger -> FilePath -> IO (Maybe LibDir)+  , getInitialGhcLibDir    :: Recorder (WithPriority Log) -> FilePath -> IO (Maybe LibDir)   , fakeUid                :: UnitId     -- ^ unit id used to tag the internal component built by ghcide     --   To reuse external interface files the unit ids must match,@@ -119,7 +216,7 @@   }  instance Default SessionLoadingOptions where-    def = SessionLoadingOptions+    def =  SessionLoadingOptions         {findCradle = HieBios.findCradle         ,loadCradle = loadWithImplicitCradle         ,getCacheDirs = getCacheDirsDefault@@ -148,26 +245,27 @@     Just yaml -> HieBios.loadCradle yaml     Nothing   -> loadImplicitHieCradle $ addTrailingPathSeparator rootDir -getInitialGhcLibDirDefault :: Logger -> FilePath -> IO (Maybe LibDir)-getInitialGhcLibDirDefault logger rootDir = do+getInitialGhcLibDirDefault :: Recorder (WithPriority Log) -> FilePath -> IO (Maybe LibDir)+getInitialGhcLibDirDefault recorder rootDir = do+  let log = logWith recorder   hieYaml <- findCradle def rootDir   cradle <- loadCradle def hieYaml rootDir-  logDebug logger $ T.pack $ "setInitialDynFlags cradle: " ++ show cradle   libDirRes <- getRuntimeGhcLibDir cradle   case libDirRes of       CradleSuccess libdir -> pure $ Just $ LibDir libdir       CradleFail err -> do-        hPutStrLn stderr $ "Couldn't load cradle for libdir: " ++ show (err,rootDir,hieYaml,cradle)+        log Error $ LogGetInitialGhcLibDirDefaultCradleFail err rootDir hieYaml cradle         pure Nothing       CradleNone -> do-        hPutStrLn stderr "Couldn't load cradle (CradleNone)"+        log Warning LogGetInitialGhcLibDirDefaultCradleNone         pure Nothing  -- | Sets `unsafeGlobalDynFlags` on using the hie-bios cradle and returns the GHC libdir-setInitialDynFlags :: Logger -> FilePath -> SessionLoadingOptions -> IO (Maybe LibDir)-setInitialDynFlags logger rootDir SessionLoadingOptions{..} = do-  libdir <- getInitialGhcLibDir logger rootDir+setInitialDynFlags :: Recorder (WithPriority Log) -> FilePath -> SessionLoadingOptions -> IO (Maybe LibDir)+setInitialDynFlags recorder rootDir SessionLoadingOptions{..} = do+  libdir <- getInitialGhcLibDir recorder rootDir   dynFlags <- mapM dynFlagsForPrinting libdir+  logWith recorder Debug LogSettingInitialDynFlags   mapM_ setUnsafeGlobalDynFlags dynFlags   pure libdir @@ -180,14 +278,14 @@ retryOnException   :: (MonadIO m, MonadCatch m, RandomGen g, Exception e)   => (e -> Maybe e) -- ^ only retry on exception if this predicate returns Just-  -> Logger+  -> Recorder (WithPriority Log)   -> Int -- ^ maximum backoff delay in microseconds   -> Int -- ^ base backoff delay in microseconds   -> Int -- ^ maximum number of times to retry   -> g -- ^ random number generator   -> m a -- ^ action that may throw exception   -> m a-retryOnException exceptionPred logger maxDelay !baseDelay !maxRetryCount rng action = do+retryOnException exceptionPred recorder maxDelay !baseDelay !maxRetryCount rng action = do   result <- tryJust exceptionPred action   case result of     Left e@@ -197,30 +295,18 @@         let (delay, newRng) = Random.randomR (0, newBaseDelay) rng         let newMaxRetryCount = maxRetryCount - 1         liftIO $ do-          logWarning logger $ "Retrying - " <> makeLogMsgComponentsText (Right delay) newMaxRetryCount e+          log Warning $ LogHieDbRetry delay maxDelay newMaxRetryCount (toException e)           threadDelay delay-        retryOnException exceptionPred logger maxDelay newBaseDelay newMaxRetryCount newRng action+        retryOnException exceptionPred recorder maxDelay newBaseDelay newMaxRetryCount newRng action        | otherwise -> do         liftIO $ do-          logWarning logger $ "Retries exhausted - " <> makeLogMsgComponentsText (Left baseDelay) maxRetryCount e+          log Warning $ LogHieDbRetriesExhausted baseDelay maxDelay maxRetryCount (toException e)           throwIO e      Right b -> pure b   where-    -- e.g. delay: 1010102, maximumDelay: 12010, maxRetryCount: 9, exception: SQLError { ... }-    makeLogMsgComponentsText delay newMaxRetryCount e =-      let-        logMsgComponents =-          [ either-              (("base delay: " <>) . T.pack . show)-              (("delay: " <>) . T.pack . show)-              delay-          , "maximumDelay: " <> T.pack (show maxDelay)-          , "maxRetryCount: " <> T.pack (show newMaxRetryCount)-          , "exception: " <> T.pack (show e)]-      in-        T.intercalate ", " logMsgComponents+    log = logWith recorder  -- | in microseconds oneSecond :: Int@@ -235,30 +321,30 @@ maxRetryCount = 10  retryOnSqliteBusy :: (MonadIO m, MonadCatch m, RandomGen g)-                  => Logger -> g -> m a -> m a-retryOnSqliteBusy logger rng action =+                  => Recorder (WithPriority Log) -> g -> m a -> m a+retryOnSqliteBusy recorder rng action =   let isErrorBusy e         | SQLError{ sqlError = ErrorBusy } <- e = Just e         | otherwise = Nothing   in-    retryOnException isErrorBusy logger oneSecond oneMillisecond maxRetryCount rng action+    retryOnException isErrorBusy recorder oneSecond oneMillisecond maxRetryCount rng action -makeWithHieDbRetryable :: RandomGen g => Logger -> g -> HieDb -> WithHieDb-makeWithHieDbRetryable logger rng hieDb f =-  retryOnSqliteBusy logger rng (f hieDb)+makeWithHieDbRetryable :: RandomGen g => Recorder (WithPriority Log) -> g -> HieDb -> WithHieDb+makeWithHieDbRetryable recorder rng hieDb f =+  retryOnSqliteBusy recorder rng (f hieDb)  -- | Wraps `withHieDb` to provide a database connection for reading, and a `HieWriterChan` for -- writing. Actions are picked off one by one from the `HieWriterChan` and executed in serial -- by a worker thread using a dedicated database connection. -- This is done in order to serialize writes to the database, or else SQLite becomes unhappy-runWithDb :: Logger -> FilePath -> (WithHieDb -> IndexQueue -> IO ()) -> IO ()-runWithDb logger fp k = do+runWithDb :: Recorder (WithPriority Log) -> FilePath -> (WithHieDb -> IndexQueue -> IO ()) -> IO ()+runWithDb recorder fp k = do   -- use non-deterministic seed because maybe multiple HLS start at same time   -- and send bursts of requests   rng <- Random.newStdGen   -- Delete the database if it has an incompatible schema version   retryOnSqliteBusy-    logger+    recorder     rng     (withHieDb fp (const $ pure ()) `Safe.catch` \IncompatibleSchemaVersion{} -> removeFile fp) @@ -267,14 +353,16 @@     -- e.g. `withWriteDbRetrable initConn` without type signature will     -- instantiate tyvar `a` to `()`     let withWriteDbRetryable :: WithHieDb-        withWriteDbRetryable = makeWithHieDbRetryable logger rng writedb+        withWriteDbRetryable = makeWithHieDbRetryable recorder rng writedb     withWriteDbRetryable initConn      chan <- newTQueueIO      withAsync (writerThread withWriteDbRetryable chan) $ \_ -> do-      withHieDb fp (\readDb -> k (makeWithHieDbRetryable logger rng readDb) chan)+      withHieDb fp (\readDb -> k (makeWithHieDbRetryable recorder rng readDb) chan)   where+    log = logWith recorder+     writerThread :: WithHieDb -> IndexQueue -> IO ()     writerThread withHieDbRetryable chan = do       -- Clear the index of any files that might have been deleted since the last run@@ -282,11 +370,12 @@       _ <- withHieDbRetryable garbageCollectTypeNames       forever $ do         k <- atomically $ readTQueue chan+        -- TODO: probably should let exceptions be caught/logged/handled by top level handler         k withHieDbRetryable           `Safe.catch` \e@SQLError{} -> do-            logDebug logger $ T.pack $ "SQLite error in worker, ignoring: " ++ show e+            log Error $ LogHieDbWriterThreadSQLiteError e           `Safe.catchAny` \e -> do-            logDebug logger $ T.pack $ "Uncaught error in database worker, ignoring: " ++ show e+            log Error $ LogHieDbWriterThreadException e   getHieDbLoc :: FilePath -> IO FilePath@@ -310,11 +399,11 @@ -- This is the key function which implements multi-component support. All -- 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 def+loadSession :: Recorder (WithPriority Log) -> FilePath -> IO (Action IdeGhcSession)+loadSession recorder = loadSessionWithOptions recorder def -loadSessionWithOptions :: SessionLoadingOptions -> FilePath -> IO (Action IdeGhcSession)-loadSessionWithOptions SessionLoadingOptions{..} dir = do+loadSessionWithOptions :: Recorder (WithPriority Log) -> SessionLoadingOptions -> FilePath -> IO (Action IdeGhcSession)+loadSessionWithOptions recorder SessionLoadingOptions{..} dir = do   -- Mapping from hie.yaml file to HscEnv, one per hie.yaml file   hscEnvs <- newVar Map.empty :: IO (Var HieMap)   -- Mapping from a Filepath to HscEnv@@ -340,7 +429,7 @@   runningCradle <- newVar dummyAs :: IO (Var (Async (IdeResult HscEnvEq,[FilePath])))    return $ do-    extras@ShakeExtras{logger, restartShakeSession, ideNc, knownTargetsVar, lspEnv+    extras@ShakeExtras{restartShakeSession, ideNc, knownTargetsVar, lspEnv                       } <- getShakeExtras     let invalidateShakeCache :: IO ()         invalidateShakeCache = do@@ -371,8 +460,7 @@             logDirtyKeys <- recordDirtyKeys extras GetKnownTargets [emptyFilePath]             return (logDirtyKeys >> pure hasUpdate)           for_ hasUpdate $ \x ->-                logDebug logger $ "Known files updated: " <>-                    T.pack(show $ (HM.map . Set.map) fromNormalizedFilePath x)+            logWith recorder Debug $ LogKnownFilesUpdated x      -- Create a new HscEnv from a hieYaml root and a set of options     -- If the hieYaml file already has an HscEnv, the new component is@@ -412,7 +500,7 @@                   let hscComponents = sort $ map show uids                       cacheDirOpts = hscComponents ++ componentOptions opts                   cacheDirs <- liftIO $ getCacheDirs prefix cacheDirOpts-                  processed_df <- setCacheDirs logger cacheDirs df2+                  processed_df <- setCacheDirs recorder cacheDirs df2                   -- The final component information, mostly the same but the DynFlags don't                   -- contain any packages which are also loaded                   -- into the same component.@@ -427,7 +515,7 @@               -- scratch again (for now)               -- It's important to keep the same NameCache though for reasons               -- that I do not fully understand-              logInfo logger (T.pack ("Making new HscEnv" ++ show inplace))+              log Info $ LogMakingNewHscEnv inplace               hscEnv <- emptyHscEnv ideNc libDir               newHscEnv <-                 -- Add the options for the current component to the HscEnv@@ -463,10 +551,10 @@             initObjLinker hscEnv             res <- loadDLL hscEnv "libm.so.6"             case res of-              Nothing -> pure ()-              Just err -> logDebug logger $ T.pack $-                "Error dynamically loading libm.so.6:\n" <> err+              Nothing  -> pure ()+              Just err -> log Error $ LogDLLLoadError err +           -- Make a map from unit-id to DynFlags, this is used when trying to           -- resolve imports. (especially PackageImports)           let uids = map (\ci -> (componentUnitId ci, componentDynFlags ci)) (new : old_deps)@@ -476,7 +564,7 @@            -- New HscEnv for the component in question, returns the new HscEnvEq and           -- a mapping from FilePath to the newly created HscEnvEq.-          let new_cache = newComponentCache logger optExtensions hieYaml _cfp hscEnv uids+          let new_cache = newComponentCache recorder optExtensions hieYaml _cfp hscEnv uids           (cs, res) <- new_cache new           -- Modified cache targets for everything else in the hie.yaml file           -- which now uses the same EPS and so on@@ -493,8 +581,10 @@            -- Invalidate all the existing GhcSession build nodes by restarting the Shake session           invalidateShakeCache-          restartShakeSession "new component" [] +          -- The VFS doesn't change on cradle edits, re-use the old one.+          restartShakeSession VFSUnmodified "new component" []+           -- Typecheck all files in the project on startup           checkProject <- getCheckProject           unless (null cs || not checkProject) $ do@@ -513,10 +603,10 @@     let consultCradle :: Maybe FilePath -> FilePath -> IO (IdeResult HscEnvEq, [FilePath])         consultCradle hieYaml cfp = do            lfp <- flip makeRelative cfp <$> getCurrentDirectory-           logInfo logger $ T.pack ("Consulting the cradle for " <> show lfp)+           log Info $ LogCradlePath lfp             when (isNothing hieYaml) $-             logWarning logger $ implicitCradleWarning lfp+             log Warning $ LogCradleNotFound lfp             cradle <- loadCradle hieYaml dir            lfp <- flip makeRelative cfp <$> getCurrentDirectory@@ -530,12 +620,11 @@            eopts <- mRunLspTCallback lspEnv (withIndefiniteProgress progMsg NotCancellable) $               withTrace "Load cradle" $ \addTag -> do                   addTag "file" lfp-                  res <- cradleToOptsAndLibDir logger cradle cfp+                  res <- cradleToOptsAndLibDir recorder cradle cfp                   addTag "result" (show res)                   return res --           logDebug logger $ T.pack ("Session loading result: " <> show eopts)+           log Debug $ LogSessionLoadingResult eopts            case eopts of              -- The cradle gave us some options so get to work turning them              -- into and HscEnv.@@ -598,16 +687,19 @@         as <- async $ getOptions file         return (as, wait as)       pure opts+  where+    log = logWith recorder  -- | Run the specific cradle on a specific FilePath via hie-bios. -- This then builds dependencies or whatever based on the cradle, gets the -- GHC options/dynflags needed for the session and the GHC library directory--cradleToOptsAndLibDir :: Show a => Logger -> Cradle a -> FilePath+cradleToOptsAndLibDir :: Recorder (WithPriority Log) -> Cradle Void -> FilePath                       -> IO (Either [CradleError] (ComponentOptions, FilePath))-cradleToOptsAndLibDir logger cradle file = do+cradleToOptsAndLibDir recorder cradle file = do+    -- let noneCradleFoundMessage :: FilePath -> T.Text+    --     noneCradleFoundMessage f = T.pack $ "none cradle found for " <> f <> ", ignoring the file"     -- Start off by getting the session options-    logDebug logger $ T.pack $ "Output from setting up the cradle " <> show cradle+    logWith recorder Debug $ LogCradle cradle     cradleRes <- HieBios.getCompilerOptions file cradle     case cradleRes of         CradleSuccess r -> do@@ -617,13 +709,14 @@                 -- This is the successful path                 CradleSuccess libDir -> pure (Right (r, libDir))                 CradleFail err       -> return (Left [err])-                -- For the None cradle perhaps we still want to report an Info-                -- message about the fact that the file is being ignored.-                CradleNone           -> return (Left [])+                CradleNone           -> do+                    logWith recorder Info $ LogNoneCradleFound file+                    return (Left [])          CradleFail err -> return (Left [err])-        -- Same here-        CradleNone -> return (Left [])+        CradleNone -> do+            logWith recorder Info $ LogNoneCradleFound file+            return (Left [])  emptyHscEnv :: IORef NameCache -> FilePath -> IO HscEnv emptyHscEnv nc libDir = do@@ -669,7 +762,7 @@  -- | Create a mapping from FilePaths to HscEnvEqs newComponentCache-         :: Logger+         :: Recorder (WithPriority Log)          -> [String]       -- File extensions to consider          -> Maybe FilePath -- Path to cradle          -> NormalizedFilePath -- Path to file that caused the creation of this component@@ -677,17 +770,26 @@          -> [(UnitId, DynFlags)]          -> ComponentInfo          -> IO ( [TargetDetails], (IdeResult HscEnvEq, DependencyInfo))-newComponentCache logger exts cradlePath cfp hsc_env uids ci = do+newComponentCache recorder exts cradlePath cfp hsc_env uids ci = do     let df = componentDynFlags ci-    let hscEnv' = hscSetFlags df hsc_env-                          { hsc_IC = (hsc_IC hsc_env) { ic_dflags = df } }+    hscEnv' <-+      -- Add the options for the current component to the HscEnv+      -- We want to call `setSessionDynFlags` instead of `hscSetFlags`+      -- because `setSessionDynFlags` also initializes the package database,+      -- which we need for any changes to the package flags in the dynflags+      -- to be visible.+      -- See #2693+      evalGhcEnv hsc_env $ do+        _ <- setSessionDynFlags $ df+        getSession +     let newFunc = maybe newHscEnvEqPreserveImportPaths newHscEnvEq cradlePath     henv <- newFunc hscEnv' uids     let targetEnv = ([], Just henv)         targetDepends = componentDependencyInfo ci         res = (targetEnv, targetDepends)-    logDebug logger ("New Component Cache HscEnvEq: " <> T.pack (show res))+    logWith recorder Debug $ LogNewComponentCache res      let mk t = fromTargetId (importPaths df) exts (targetId t) targetEnv targetDepends     ctargets <- concatMapM mk (componentTargets ci)@@ -755,9 +857,9 @@ -- | Set the cache-directory based on the ComponentOptions and a list of -- internal packages. -- 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 (fromMaybe cacheDir hiCacheDir)+setCacheDirs :: MonadUnliftIO m => Recorder (WithPriority Log) -> CacheDirs -> DynFlags -> m DynFlags+setCacheDirs recorder CacheDirs{..} dflags = do+    logWith recorder Info $ LogInterfaceFilesCacheDir (fromMaybe cacheDir hiCacheDir)     pure $ dflags           & maybe id setHiDir hiCacheDir           & maybe id setHieDir hieCacheDir@@ -930,12 +1032,6 @@ cacheDir :: String cacheDir = "ghcide" -implicitCradleWarning :: FilePath -> T.Text-implicitCradleWarning fp =-  "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.hs view
@@ -17,8 +17,7 @@                                                              isWorkspaceFile) import           Development.IDE.Core.OfInterest       as X (getFilesOfInterestUntracked) import           Development.IDE.Core.RuleTypes        as X-import           Development.IDE.Core.Rules            as X (IsHiFileStable (..),-                                                             getClientConfigAction,+import           Development.IDE.Core.Rules            as X (getClientConfigAction,                                                              getParsedModule) import           Development.IDE.Core.Service          as X (runAction) import           Development.IDE.Core.Shake            as X (FastResult (..),@@ -41,7 +40,8 @@                                                              useWithStaleFast,                                                              useWithStaleFast',                                                              useWithStale_,-                                                             use_, uses, uses_)+                                                             use_, uses, uses_,+                                                             VFSModified(..)) import           Development.IDE.GHC.Compat            as X (GhcVersion (..),                                                              ghcVersion) import           Development.IDE.GHC.Error             as X
src/Development/IDE/Core/Compile.hs view
@@ -26,14 +26,56 @@   , getModSummaryFromImports   , loadHieFile   , loadInterface+  , RecompilationInfo(..)   , loadModulesHome   , getDocsBatch   , lookupName-  ,mergeEnvs) where+  , mergeEnvs+  ) where +import           Control.Concurrent.Extra+import           Control.Concurrent.STM.Stats      hiding (orElse)+import           Control.DeepSeq                   (force, liftRnf, rnf, rwhnf)+import           Control.Exception                 (evaluate)+import           Control.Exception.Safe+import           Control.Lens                      hiding (List)+import           Control.Monad.Except+import           Control.Monad.Extra+import           Control.Monad.Trans.Except+import           Data.Aeson                        (toJSON)+import           Data.Bifunctor                    (first, second)+import           Data.Binary+import qualified Data.Binary                       as B+import qualified Data.ByteString                   as BS+import qualified Data.ByteString.Lazy              as LBS+import           Data.Coerce+import qualified Data.DList                        as DL+import           Data.Functor+import qualified Data.HashMap.Strict               as HashMap+import           Data.IORef+import           Data.IntMap                       (IntMap)+import qualified Data.IntMap.Strict                as IntMap+import           Data.List.Extra+import           Data.Map                          (Map)+import qualified Data.Map.Strict                   as Map+import           Data.Maybe+import qualified Data.Text                         as T+import           Data.Time                         (UTCTime (..),+                                                    getCurrentTime)+import           Data.Time.Clock.POSIX             (posixSecondsToUTCTime)+import           Data.Tuple.Extra                  (dupe)+import           Data.Unique                       as Unique+import           Debug.Trace import           Development.IDE.Core.Preprocessor import           Development.IDE.Core.RuleTypes import           Development.IDE.Core.Shake+import           Development.IDE.Core.Tracing      (withTrace)+import           Development.IDE.GHC.Compat        hiding (loadInterface,+                                                    parseHeader, parseModule,+                                                    tcRnModule, writeHieFile)+import qualified Development.IDE.GHC.Compat        as Compat+import qualified Development.IDE.GHC.Compat        as GHC+import qualified Development.IDE.GHC.Compat.Util   as Util import           Development.IDE.GHC.Error import           Development.IDE.GHC.Orphans       () import           Development.IDE.GHC.Util@@ -42,26 +84,25 @@ import           Development.IDE.Types.Diagnostics import           Development.IDE.Types.Location import           Development.IDE.Types.Options--import           Development.IDE.GHC.Compat        hiding (loadInterface,-                                                    parseHeader, parseModule,-                                                    tcRnModule, writeHieFile)-import qualified Development.IDE.GHC.Compat        as Compat-import qualified Development.IDE.GHC.Compat        as GHC-import qualified Development.IDE.GHC.Compat.Util   as Util-+import           GHC                               (ForeignHValue,+                                                    GetDocsFailure (..),+                                                    mgModSummaries,+                                                    parsedSource)+import qualified GHC.LanguageExtensions            as LangExt+import           GHC.Serialized import           HieDb-+import qualified Language.LSP.Server               as LSP import           Language.LSP.Types                (DiagnosticTag (..))+import qualified Language.LSP.Types                as LSP+import           System.Directory+import           System.FilePath+import           System.IO.Extra                   (fixIO, newTempFileWithin)+import           Unsafe.Coerce -#if MIN_VERSION_ghc(8,10,0)-import           Control.DeepSeq                   (force, liftRnf, rnf, rwhnf)-#else-import           Control.DeepSeq                   (liftRnf, rnf, rwhnf)+#if !MIN_VERSION_ghc(8,10,0) import           ErrUtils #endif - #if MIN_VERSION_ghc(9,0,1) import           GHC.Tc.Gen.Splice #else@@ -69,53 +110,18 @@ #endif  #if MIN_VERSION_ghc(9,2,0)+import           Development.IDE.GHC.Compat.Util   (emptyUDFM, fsLit,+                                                    plusUDFM_C)+import           GHC                               (Anchor (anchor),+                                                    EpaComment (EpaComment),+                                                    EpaCommentTok (EpaBlockComment, EpaLineComment),+                                                    epAnnComments,+                                                    priorComments)+import qualified GHC                               as G+import           GHC.Hs                            (LEpaComment) import qualified GHC.Types.Error                   as Error #endif -import           Control.Exception                 (evaluate)-import           Control.Exception.Safe-import           Control.Lens                      hiding (List)-import           Control.Monad.Except-import           Control.Monad.Extra-import           Control.Monad.Trans.Except-import           Data.Bifunctor                    (first, second)-import qualified Data.ByteString                   as BS-import qualified Data.DList                        as DL-import           Data.IORef-import qualified Data.IntMap.Strict                as IntMap-import           Data.List.Extra-import qualified Data.Map.Strict                   as Map-import           Data.Maybe-import qualified Data.Text                         as T-import           Data.Time                         (UTCTime, getCurrentTime)-import qualified GHC.LanguageExtensions            as LangExt-import           System.Directory-import           System.FilePath-import           System.IO.Extra                   (fixIO, newTempFileWithin)---- GHC API imports--- GHC API imports-import           GHC                               (GetDocsFailure (..),-                                                    mgModSummaries,-                                                    parsedSource)--import           Control.Concurrent.Extra-import           Control.Concurrent.STM.Stats      hiding (orElse)-import           Data.Aeson                        (toJSON)-import           Data.Binary-import           Data.Coerce-import           Data.Functor-import qualified Data.HashMap.Strict               as HashMap-import           Data.IntMap                       (IntMap)-import           Data.Map                          (Map)-import           Data.Tuple.Extra                  (dupe)-import           Data.Unique                       as Unique-import           Development.IDE.Core.Tracing      (withTrace)-import           Development.IDE.GHC.Compat.Util   (emptyUDFM, plusUDFM_C)-import qualified Language.LSP.Server               as LSP-import qualified Language.LSP.Types                as LSP-import           Unsafe.Coerce- -- | Given a string buffer, return the string (after preprocessing) and the 'ParsedModule'. parseModule     :: IdeOptions@@ -143,7 +149,7 @@  typecheckModule :: IdeDefer                 -> HscEnv-                -> [Linkable] -- ^ linkables not to unload+                -> ModuleEnv UTCTime -- ^ linkables not to unload                 -> ParsedModule                 -> IO (IdeResult TcModuleResult) typecheckModule (IdeDefer defer) hsc keep_lbls pm = do@@ -171,16 +177,110 @@     where         demoteIfDefer = if defer then demoteTypeErrorsToWarnings else id --- | Add a Hook to the DynFlags which captures and returns the--- typechecked splices before they are run. This information--- is used for hover.-captureSplices :: HscEnv -> (HscEnv -> IO a) -> IO (a, Splices)-captureSplices env k = do+-- | Install hooks to capture the splices as well as the runtime module dependencies+captureSplicesAndDeps :: HscEnv -> (HscEnv -> IO a) -> IO (a, Splices, UniqSet ModuleName)+captureSplicesAndDeps env k = do   splice_ref <- newIORef mempty-  res <- k (hscSetHooks (addSpliceHook splice_ref (hsc_hooks env)) env)+  dep_ref <- newIORef emptyUniqSet+  res <- k (hscSetHooks (addSpliceHook splice_ref . addLinkableDepHook dep_ref $ hsc_hooks env) env)   splices <- readIORef splice_ref-  return (res, splices)+  needed_mods <- readIORef dep_ref+  return (res, splices, needed_mods)   where+    addLinkableDepHook :: IORef (UniqSet ModuleName) -> Hooks -> Hooks+    addLinkableDepHook var h = h { hscCompileCoreExprHook = Just (compile_bco_hook var) }++    -- We want to record exactly which linkables/modules the typechecker needed at runtime+    -- This is useful for recompilation checking.+    -- See Note [Recompilation avoidance in the presence of TH]+    --+    -- From hscCompileCoreExpr' in GHC+    -- To update, copy hscCompileCoreExpr' (the implementation of+    -- hscCompileCoreExprHook) verbatim, and add code to extract all the free+    -- names in the compiled bytecode, recording the modules that those names+    -- come from in the IORef,, as these are the modules on whose implementation+    -- we depend.+    --+    -- Only compute direct dependencies instead of transitive dependencies.+    -- It is much cheaper to store the direct dependencies, we can compute+    -- the transitive ones when required.+    -- Also only record dependencies from the home package+    compile_bco_hook :: IORef (UniqSet ModuleName) -> HscEnv -> SrcSpan -> CoreExpr -> IO ForeignHValue+    compile_bco_hook var hsc_env srcspan ds_expr+      = do { let dflags = hsc_dflags hsc_env++             {- Simplify it -}+           ; simpl_expr <- simplifyExpr dflags hsc_env ds_expr++             {- Tidy it (temporary, until coreSat does cloning) -}+           ; let tidy_expr = tidyExpr emptyTidyEnv simpl_expr++             {- Prepare for codegen -}+           ; prepd_expr <- corePrepExpr dflags hsc_env tidy_expr++             {- Lint if necessary -}+           ; lintInteractiveExpr "hscCompileExpr" hsc_env prepd_expr+++#if MIN_VERSION_ghc(9,2,0)+           ; let iNTERACTIVELoc = G.ModLocation{ ml_hs_file   = Nothing,+                                        ml_hi_file   = panic "hscCompileCoreExpr':ml_hi_file",+                                        ml_obj_file  = panic "hscCompileCoreExpr':ml_obj_file",+                                        ml_hie_file  = panic "hscCompileCoreExpr':ml_hie_file" }+           ; let ictxt = hsc_IC hsc_env++           ; (binding_id, stg_expr, _, _) <-+               myCoreToStgExpr (hsc_logger hsc_env)+                               (hsc_dflags hsc_env)+                               ictxt+                               (icInteractiveModule ictxt)+                               iNTERACTIVELoc+                               prepd_expr++             {- Convert to BCOs -}+           ; bcos <- byteCodeGen hsc_env+                       (icInteractiveModule ictxt)+                       stg_expr+                       [] Nothing+           ; let needed_mods = mkUniqSet [ moduleName mod | n <- concatMap (uniqDSetToList . bcoFreeNames) $ bc_bcos bcos+                                         , Just mod <- [nameModule_maybe n] -- Names from other modules+                                         , not (isWiredInName n) -- Exclude wired-in names+                                         , moduleUnitId mod == homeUnitId_ dflags -- Only care about stuff from the home package+                                         ]+            -- Exclude wired-in names because we may not have read+            -- their interface files, so getLinkDeps will fail+            -- All wired-in names are in the base package, which we link+            -- by default, so we can safely ignore them here.++             {- load it -}+           ; fv_hvs <- loadDecls (hscInterp hsc_env) hsc_env srcspan bcos+           ; let hval = (expectJust "hscCompileCoreExpr'" $ lookup (idName binding_id) fv_hvs)+#else+             {- Convert to BCOs -}+           ; bcos <- coreExprToBCOs hsc_env+                       (icInteractiveModule (hsc_IC hsc_env)) prepd_expr++           ; let needed_mods = mkUniqSet [ moduleName mod | n <- uniqDSetToList (bcoFreeNames bcos)+                                         , Just mod <- [nameModule_maybe n] -- Names from other modules+                                         , not (isWiredInName n) -- Exclude wired-in names+                                         , moduleUnitId mod == homeUnitId_ dflags -- Only care about stuff from the home package+                                         ]+            -- Exclude wired-in names because we may not have read+            -- their interface files, so getLinkDeps will fail+            -- All wired-in names are in the base package, which we link+            -- by default, so we can safely ignore them here.++             {- link it -}+           ; hval <- linkExpr hsc_env srcspan bcos+#endif++           ; modifyIORef' var (unionUniqSets needed_mods)+           ; return hval }+++    -- | Add a Hook to the DynFlags which captures and returns the+    -- typechecked splices before they are run. This information+    -- is used for hover.     addSpliceHook :: IORef Splices -> Hooks -> Hooks     addSpliceHook var h = h { runMetaHook = Just (splice_hook (runMetaHook h) var) } @@ -208,15 +308,20 @@             pure $ f aw'  -tcRnModule :: HscEnv -> [Linkable] -> ParsedModule -> IO TcModuleResult+tcRnModule+  :: HscEnv+  -> ModuleEnv UTCTime -- ^ Program linkables not to unload+  -> ParsedModule+  -> IO TcModuleResult tcRnModule hsc_env keep_lbls pmod = do   let ms = pm_mod_summary pmod       hsc_env_tmp = hscSetFlags (ms_hspp_opts ms) hsc_env+      hpt = hsc_HPT hsc_env -  unload hsc_env_tmp keep_lbls+  unload hsc_env_tmp $ map (\(mod, time) -> LM time mod []) $ moduleEnvToList keep_lbls -  ((tc_gbl_env, mrn_info), splices)-      <- liftIO $ captureSplices hsc_env_tmp $ \hsc_env_tmp ->+  ((tc_gbl_env', mrn_info), splices, mods)+      <- captureSplicesAndDeps hsc_env_tmp $ \hsc_env_tmp ->              do  hscTypecheckRename hsc_env_tmp ms $                           HsParsedModule { hpm_module = parsedSource pmod,                                            hpm_src_files = pm_extra_src_files pmod,@@ -224,8 +329,30 @@   let rn_info = case mrn_info of         Just x  -> x         Nothing -> error "no renamed info tcRnModule"-  pure (TcModuleResult pmod rn_info tc_gbl_env splices False) +      -- Compute the transitive set of linkables required+      mods_transitive = go emptyUniqSet mods+        where+          go seen new+            | isEmptyUniqSet new = seen+            | otherwise = go seen' new'+              where+                seen' = seen `unionUniqSets` new+                new'  = new_deps `minusUniqSet` seen'+                new_deps = unionManyUniqSets [ mkUniqSet $ getDependentMods $ hm_iface mod_info+                                             | mod_info <- eltsUDFM $ udfmIntersectUFM hpt (getUniqSet new)]++      -- The linkables we depend on at runtime are the transitive closure of 'mods'+      -- restricted to the home package+      -- See Note [Recompilation avoidance in the presence of TH]+      mod_env = filterModuleEnv (\m _ -> elementOfUniqSet (moduleName m) mods_transitive) keep_lbls -- Could use restrictKeys if the constructors were exported++      -- Serialize mod_env so we can read it from the interface+      mod_env_anns = map (\(mod, time) -> Annotation (ModuleTarget mod) $ toSerialized serializeModDepTime (ModDepTime time))+                         (moduleEnvToList mod_env)+      tc_gbl_env = tc_gbl_env' { tcg_ann_env = extendAnnEnvList (tcg_ann_env tc_gbl_env') mod_env_anns }+  pure (TcModuleResult pmod rn_info tc_gbl_env splices False mod_env)+ mkHiFileResultNoCompile :: HscEnv -> TcModuleResult -> IO HiFileResult mkHiFileResultNoCompile session tcm = do   let hsc_env_tmp = hscSetFlags (ms_hspp_opts ms) session@@ -239,7 +366,7 @@   (iface, _) <- mkIfaceTc hsc_env_tmp Nothing sf details tcGblEnv #endif   let mod_info = HomeModInfo iface details Nothing-  pure $! mkHiFileResult ms mod_info+  pure $! mkHiFileResult ms mod_info (tmrRuntimeModules tcm)  mkHiFileResultCompile     :: HscEnv@@ -277,7 +404,7 @@   (final_iface,_) <- mkIface session Nothing details simplified_guts #endif   let mod_info = HomeModInfo final_iface details linkable-  pure (diags, Just $! mkHiFileResult ms mod_info)+  pure (diags, Just $! mkHiFileResult ms mod_info (tmrRuntimeModules tcm))    where     dflags = hsc_dflags session'@@ -932,56 +1059,211 @@ loadHieFile ncu f = do   GHC.hie_file_result <$> GHC.readHieFile ncu f ++{- Note [Recompilation avoidance in the presence of TH]++Most versions of GHC we currently support don't have a working implementation of+code unloading for object code, and no version of GHC supports this on certain+platforms like Windows. This makes it completely infeasible for interactive use,+as symbols from previous compiles will shadow over all future compiles.++This means that we need to use bytecode when generating code for Template+Haskell. Unfortunately, we can't serialize bytecode, so we will always need+to recompile when the IDE starts. However, we can put in place a much tighter+recompilation avoidance scheme for subsequent compiles:++1. If the source file changes, then we always need to recompile+   a. For files of interest, we will get explicit `textDocument/change` events+   that will let us invalidate our build products+   b. For files we read from disk, we can detect source file changes by+   comparing the `mtime` of the source file with the build product (.hi/.o) file+   on disk.+2. If GHC's recompilation avoidance scheme based on interface file hashes says+   that we need to recompile, the we need to recompile.+3. If the file in question requires code generation then, we need to recompile+   if we don't have the appropriate kind of build products.+   a. If we already have the build products in memory, and the conditions 1 and+   2 above hold, then we don't need to recompile+   b. If we are generating object code, then we can also search for it on+   disk and ensure it is up to date. Notably, we did _not_ previously re-use+   old bytecode from memory when `hls-graph`/`shake` decided to rebuild the+   `HiFileResult` for some reason++4. If the file in question used Template Haskell on the previous compile, then+  we need to recompile if any `Linkable` in its transitive closure changed. This+  sounds bad, but it is possible to make some improvements.+  In particular, we only need to recompile if any of the `Linkable`s actually used during the previous compile change.++How can we tell if a `Linkable` was actually used while running some TH?++GHC provides a `hscCompileCoreExprHook` which lets us intercept bytecode as+it is being compiled and linked. We can inspect the bytecode to see which+`Linkable` dependencies it requires, and record this for use in+recompilation checking.+We record all the home package modules of the free names that occur in the+bytecode. The `Linkable`s required are then the transitive closure of these+modules in the home-package environment. This is the same scheme as used by+GHC to find the correct things to link in before running bytecode.++This works fine if we already have previous build products in memory, but+what if we are reading an interface from disk? Well, we can smuggle in the+necessary information (linkable `Module`s required as well as the time they+were generated) using `Annotation`s, which provide a somewhat general purpose+way to serialise arbitrary information along with interface files.++Then when deciding whether to recompile, we need to check that the versions+of the linkables used during a previous compile match whatever is currently+in the HPT.+-}++data RecompilationInfo m+  = RecompilationInfo+  { source_version :: FileVersion+  , old_value   :: Maybe (HiFileResult, FileVersion)+  , get_file_version :: NormalizedFilePath -> m (Maybe FileVersion)+  , regenerate  :: Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult) -- ^ Action to regenerate an interface+  }+ -- | Retuns an up-to-date module interface, regenerating if needed. --   Assumes file exists. --   Requires the 'HscEnv' to be set up with dependencies+-- See Note [Recompilation avoidance in the presence of TH] loadInterface   :: (MonadIO m, MonadMask m)   => HscEnv   -> ModSummary-  -> SourceModified   -> Maybe LinkableType-  -> (Maybe LinkableType -> m ([FileDiagnostic], Maybe HiFileResult)) -- ^ Action to regenerate an interface+  -> RecompilationInfo m   -> m ([FileDiagnostic], Maybe HiFileResult)-loadInterface session ms sourceMod linkableNeeded regen = do+loadInterface session ms linkableNeeded RecompilationInfo{..} = do     let sessionWithMsDynFlags = hscSetFlags (ms_hspp_opts ms) session-    res <- liftIO $ checkOldIface sessionWithMsDynFlags ms sourceMod Nothing-    case res of-          (UpToDate, Just iface)-            -- If the module used TH splices when it was last-            -- compiled, then the recompilation check is not-            -- accurate enough (https://gitlab.haskell.org/ghc/ghc/-/issues/481)-            -- and we must ignore-            -- it.  However, if the module is stable (none of-            -- the modules it depends on, directly or-            -- indirectly, changed), then we *can* skip-            -- recompilation. This is why the SourceModified-            -- type contains SourceUnmodifiedAndStable, and-            -- it's pretty important: otherwise ghc --make-            -- would always recompile TH modules, even if-            -- nothing at all has changed. Stability is just-            -- the same check that make is doing for us in-            -- one-shot mode.-            | not (mi_used_th iface) || SourceUnmodifiedAndStable == sourceMod-            -> do-             linkable <- case linkableNeeded of-               Just ObjectLinkable -> liftIO $ findObjectLinkableMaybe (ms_mod ms) (ms_location ms)-               _ -> pure Nothing+        mb_old_iface = hm_iface    . hirHomeMod . fst <$> old_value+        mb_old_version = snd <$> old_value -             -- We don't need to regenerate if the object is up do date, or we don't need one-             let objUpToDate = isNothing linkableNeeded || case linkable of-                   Nothing                -> False-                   Just (LM obj_time _ _) -> obj_time > ms_hs_date ms-             if objUpToDate-             then do-               hmi <- liftIO $ mkDetailsFromIface sessionWithMsDynFlags iface linkable-               return ([], Just $ mkHiFileResult ms hmi)-             else regen linkableNeeded-          (_reason, _) -> withTrace "regenerate interface" $ \setTag -> do-                 setTag "Module" $ moduleNameString $ moduleName $ ms_mod ms-                 setTag "Reason" $ showReason _reason-                 regen linkableNeeded+        obj_file = ml_obj_file (ms_location ms) +        !mod = ms_mod ms++    mb_dest_version <- case mb_old_version of+      Just ver -> pure $ Just ver+      Nothing ->  get_file_version $ toNormalizedFilePath' $ case linkableNeeded of+          Just ObjectLinkable -> ml_obj_file (ms_location ms)+          _                   -> ml_hi_file (ms_location ms)++    -- The source is modified if it is newer than the destination+    let sourceMod = case mb_dest_version of+          Nothing -> SourceModified -- desitination file doesn't exist, assume modified source+          Just dest_version+            | source_version <= dest_version -> SourceUnmodified+            | otherwise -> SourceModified++    -- If mb_old_iface is nothing then checkOldIface will load it for us+    (recomp_iface_reqd, mb_checked_iface)+      <- liftIO $ checkOldIface sessionWithMsDynFlags ms sourceMod mb_old_iface+++    let+      (recomp_obj_reqd, mb_linkable) = case linkableNeeded of+        Nothing -> (UpToDate, Nothing)+        Just linkableType -> case old_value of+          -- We don't have an old result+          Nothing -> recompMaybeBecause "missing"+          -- We have an old result+          Just (old_hir, old_file_version) ->+            case hm_linkable $ hirHomeMod old_hir of+              Nothing -> recompMaybeBecause "missing [not needed before]"+              Just old_lb+                | Just True <- mi_used_th <$> mb_checked_iface -- No need to recompile if TH wasn't used+                , old_file_version /= source_version -> recompMaybeBecause "out of date"++                -- Check if it is the correct type+                -- Ideally we could use object-code in case we already have+                -- it when we are generating bytecode, but this is difficult because something+                -- below us may be bytecode, and object code can't depend on bytecode+                | ObjectLinkable <- linkableType, isObjectLinkable old_lb+                -> (UpToDate, Just old_lb)++                | BCOLinkable    <- linkableType , not (isObjectLinkable old_lb)+                -> (UpToDate, Just old_lb)++                | otherwise -> recompMaybeBecause "missing [wrong type]"+          where+            recompMaybeBecause msg = case linkableType of+              BCOLinkable -> (RecompBecause ("bytecode "++ msg), Nothing)+              ObjectLinkable -> case mb_dest_version of -- The destination file should be the object code+                Nothing -> (RecompBecause ("object code "++ msg), Nothing)+                Just disk_obj_version@(ModificationTime t) ->+                  -- If we make it this far, assume that the object code on disk is up to date+                  -- This assertion works because of the sourceMod check+                  assert (disk_obj_version >= source_version)+                         (UpToDate, Just $ LM (posixSecondsToUTCTime t) mod [DotO obj_file])+                Just (VFSVersion _) -> error "object code in vfs"++    let do_regenerate _reason = withTrace "regenerate interface" $ \setTag -> do+          setTag "Module" $ moduleNameString $ moduleName mod+          setTag "Reason" $ showReason _reason+          liftIO $ traceMarkerIO $ "regenerate interface " ++ show (moduleNameString $ moduleName mod, showReason _reason)+          regenerate linkableNeeded++    case (mb_checked_iface, recomp_iface_reqd <> recomp_obj_reqd) of+      (Just iface, UpToDate) -> do+         -- Force it because we don't want to retain old modsummaries or linkables+         lb <- liftIO $ evaluate $ force mb_linkable++         -- If we have an old value, just return it+         case old_value of+           Just (old_hir, _)+             | Just msg <- checkLinkableDependencies (hsc_HPT sessionWithMsDynFlags) (hirRuntimeModules old_hir)+             -> do_regenerate msg+             | otherwise -> return ([], Just old_hir)+           Nothing -> do+             hmi <- liftIO $ mkDetailsFromIface sessionWithMsDynFlags iface lb+             -- parse the runtime dependencies from the annotations+             let runtime_deps+                   | not (mi_used_th iface) = emptyModuleEnv+                   | otherwise = parseRuntimeDeps (md_anns (hm_details hmi))+             return ([], Just $ mkHiFileResult ms hmi runtime_deps)+      (_, _reason) -> do_regenerate _reason++-- | ModDepTime is stored as an annotation in the iface to+-- keep track of runtime dependencies+newtype ModDepTime = ModDepTime UTCTime++deserializeModDepTime :: [Word8] -> ModDepTime+deserializeModDepTime xs = ModDepTime $ case decode (LBS.pack xs) of+  (a,b) -> UTCTime (toEnum a) (toEnum b)++serializeModDepTime :: ModDepTime -> [Word8]+serializeModDepTime (ModDepTime l) = LBS.unpack $+  B.encode (fromEnum $ utctDay l, fromEnum $ utctDayTime l)++-- | Find the runtime dependencies by looking at the annotations+-- serialized in the iface+parseRuntimeDeps :: [ModIfaceAnnotation] -> ModuleEnv UTCTime+parseRuntimeDeps anns = mkModuleEnv $ mapMaybe go anns+  where+    go (Annotation (ModuleTarget mod) payload)+      | Just (ModDepTime t) <- fromSerialized deserializeModDepTime payload+      = Just (mod, t)+    go _ = Nothing++-- | checkLinkableDependencies compares the linkables in the home package to+-- the runtime dependencies of the module, to check if any of them are out of date+-- Hopefully 'runtime_deps' will be empty if the module didn't actually use TH+-- See Note [Recompilation avoidance in the presence of TH]+checkLinkableDependencies :: HomePackageTable -> ModuleEnv UTCTime -> Maybe RecompileRequired+checkLinkableDependencies hpt runtime_deps+  | isEmptyModuleEnv out_of_date = Nothing -- Nothing out of date, so don't recompile+  | otherwise = Just $+      RecompBecause $ "out of date runtime dependencies: " ++ intercalate ", " (map show (moduleEnvKeys out_of_date))+  where+    out_of_date = filterModuleEnv (\mod time -> case lookupHpt hpt (moduleName mod) of+                                                  Nothing -> False+                                                  Just hm -> case hm_linkable hm of+                                                    Nothing -> False+                                                    Just lm -> linkableTime lm /= time)+                                  runtime_deps+ showReason :: RecompileRequired -> String showReason UpToDate          = "UpToDate" showReason MustCompile       = "MustCompile"@@ -1019,7 +1301,7 @@ #endif                                   Map.findWithDefault mempty name amap))     case res of-        Just x  -> return $ map (first $ T.unpack . showGhc) x+        Just x  -> return $ map (first $ T.unpack . printOutputable) x         Nothing -> throwErrors #if MIN_VERSION_ghc(9,2,0)                      $ Error.getErrorMessages msgs
src/Development/IDE/Core/FileExists.hs view
@@ -7,6 +7,7 @@   , getFileExists   , watchedGlobs   , GetFileExists(..)+  , Log(..)   ) where @@ -18,12 +19,17 @@ import qualified Data.ByteString                       as BS import           Data.List                             (partition) import           Data.Maybe-import           Development.IDE.Core.FileStore+import           Development.IDE.Core.FileStore        hiding (Log, LogShake)+import qualified Development.IDE.Core.FileStore        as FileStore import           Development.IDE.Core.IdeConfiguration import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Shake            hiding (Log)+import qualified Development.IDE.Core.Shake            as Shake import           Development.IDE.Graph import           Development.IDE.Types.Location+import           Development.IDE.Types.Logger          (Pretty (pretty),+                                                        Recorder, WithPriority,+                                                        cmapWithPrio) import           Development.IDE.Types.Options import qualified Focus import           Ide.Plugin.Config                     (Config)@@ -82,6 +88,16 @@  instance IsIdeGlobal FileExistsMapVar +data Log+  = LogFileStore FileStore.Log+  | LogShake Shake.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogFileStore log -> pretty log+    LogShake log     -> pretty log+ -- | Grab the current global value of 'FileExistsMap' without acquiring a dependency getFileExistsMapUntracked :: Action FileExistsMap getFileExistsMapUntracked = do@@ -93,8 +109,7 @@ modifyFileExists state changes = do   FileExistsMapVar var <- getIdeGlobalState state   -- Masked to ensure that the previous values are flushed together with the map update-    -- update the map-  mask_ $ join $ atomicallyNamed "modifyFileExists" $ do+  join $ mask_ $ atomicallyNamed "modifyFileExists" $ do     forM_ changes $ \(f,c) ->         case fromChange c of             Just c' -> STM.focus (Focus.insert c') f var@@ -116,14 +131,6 @@ -------------------------------------------------------------------------------------  -- | Returns True if the file exists---   Note that a file is not considered to exist unless it is saved to disk.---   In particular, VFS existence is not enough.---   Consider the following example:---     1. The file @A.hs@ containing the line @import B@ is added to the files of interest---        Since @B.hs@ is neither open nor exists, GetLocatedImports finds Nothing---     2. The editor creates a new buffer @B.hs@---        Unless the editor also sends a @DidChangeWatchedFile@ event, ghcide will not pick it up---        Most editors, e.g. VSCode, only send the event when the file is saved to disk. getFileExists :: NormalizedFilePath -> Action Bool getFileExists fp = use_ GetFileExists fp @@ -157,8 +164,8 @@ -- | 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 :: Maybe (LanguageContextEnv Config) -> VFSHandle -> Rules ()-fileExistsRules lspEnv vfs = do+fileExistsRules :: Recorder (WithPriority Log) -> Maybe (LanguageContextEnv Config) -> Rules ()+fileExistsRules recorder lspEnv = do   supportsWatchedFiles <- case lspEnv of     Nothing      -> pure False     Just lspEnv' -> liftIO $  runLspT lspEnv' isWatchSupported@@ -179,19 +186,19 @@         else const $ pure False    if supportsWatchedFiles-    then fileExistsRulesFast isWatched vfs-    else fileExistsRulesSlow vfs+    then fileExistsRulesFast recorder isWatched+    else fileExistsRulesSlow recorder -  fileStoreRules vfs isWatched+  fileStoreRules (cmapWithPrio LogFileStore recorder) isWatched  -- Requires an lsp client that provides WatchedFiles notifications, but assumes that this has already been checked.-fileExistsRulesFast :: (NormalizedFilePath -> Action Bool) -> VFSHandle -> Rules ()-fileExistsRulesFast isWatched vfs =-    defineEarlyCutoff $ RuleNoDiagnostics $ \GetFileExists file -> do+fileExistsRulesFast :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()+fileExistsRulesFast recorder isWatched =+    defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetFileExists file -> do         isWF <- isWatched file         if isWF-            then fileExistsFast vfs file-            else fileExistsSlow vfs file+            then fileExistsFast file+            else fileExistsSlow file  {- Note [Invalidating file existence results] We have two mechanisms for getting file existence information:@@ -209,8 +216,8 @@ we use 'alwaysRerun'. -} -fileExistsFast :: VFSHandle -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool)-fileExistsFast vfs file = do+fileExistsFast :: NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool)+fileExistsFast file = do     -- Could in principle use 'alwaysRerun' here, but it's too slwo, See Note [Invalidating file existence results]     mp <- getFileExistsMapUntracked @@ -219,28 +226,27 @@       Just exist -> pure exist       -- We don't know about it: use the slow route.       -- Note that we do *not* call 'fileExistsSlow', as that would trigger 'alwaysRerun'.-      Nothing    -> liftIO $ getFileExistsVFS vfs file+      Nothing    -> getFileExistsVFS file     pure (summarizeExists exist, Just exist)  summarizeExists :: Bool -> Maybe BS.ByteString summarizeExists x = Just $ if x then BS.singleton 1 else BS.empty -fileExistsRulesSlow :: VFSHandle -> Rules ()-fileExistsRulesSlow vfs =-  defineEarlyCutoff $ RuleNoDiagnostics $ \GetFileExists file -> fileExistsSlow vfs file+fileExistsRulesSlow :: Recorder (WithPriority Log) -> Rules ()+fileExistsRulesSlow recorder =+  defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetFileExists file -> fileExistsSlow file -fileExistsSlow :: VFSHandle -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool)-fileExistsSlow vfs file = do+fileExistsSlow :: NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe Bool)+fileExistsSlow file = do     -- See Note [Invalidating file existence results]     alwaysRerun-    exist <- liftIO $ getFileExistsVFS vfs file+    exist <- getFileExistsVFS file     pure (summarizeExists exist, Just exist) -getFileExistsVFS :: VFSHandle -> NormalizedFilePath -> IO Bool-getFileExistsVFS vfs file = do-    -- we deliberately and intentionally wrap the file as an FilePath WITHOUT mkAbsolute-    -- so that if the file doesn't exist, is on a shared drive that is unmounted etc we get a properly-    -- cached 'No' rather than an exception in the wrong place-    handle (\(_ :: IOException) -> return False) $-        (isJust <$> getVirtualFile vfs (filePathToUri' file)) ||^-        Dir.doesFileExist (fromNormalizedFilePath file)+getFileExistsVFS :: NormalizedFilePath -> Action Bool+getFileExistsVFS file = do+  vf <- getVirtualFile file+  if isJust vf+  then pure True+  else liftIO $ handle (\(_ :: IOException) -> return False) $+         Dir.doesFileExist (fromNormalizedFilePath file)
src/Development/IDE/Core/FileStore.hs view
@@ -5,15 +5,11 @@  module Development.IDE.Core.FileStore(     getFileContents,-    getVirtualFile,     setFileModified,     setSomethingModified,     fileStoreRules,     modificationTime,     typecheckParents,-    VFSHandle,-    makeVFSHandle,-    makeLSPVFSHandle,     resetFileStore,     resetInterfaceStore,     getModificationTimeImpl,@@ -21,26 +17,25 @@     getFileContentsImpl,     getModTime,     isWatchSupported,-    registerFileWatches+    registerFileWatches,+    Log(..)     ) where  import           Control.Concurrent.STM.Stats                 (STM, atomically,                                                                modifyTVar') import           Control.Concurrent.STM.TQueue                (writeTQueue)-import           Control.Concurrent.Strict import           Control.Exception import           Control.Monad.Extra import           Control.Monad.IO.Class import qualified Data.ByteString                              as BS import           Data.Either.Extra-import qualified Data.Map.Strict                              as Map-import           Data.Maybe import qualified Data.Rope.UTF16                              as Rope import qualified Data.Text                                    as T import           Data.Time import           Data.Time.Clock.POSIX import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Shake                   hiding (Log)+import           Development.IDE.Core.FileUtils import           Development.IDE.GHC.Orphans                  () import           Development.IDE.Graph import           Development.IDE.Import.DependencyInformation@@ -55,8 +50,6 @@ #ifdef mingw32_HOST_OS import qualified System.Directory                             as Dir #else-import           System.Posix.Files                           (getFileStatus,-                                                               modificationTimeHiRes) #endif  import qualified Development.IDE.Types.Logger                 as L@@ -67,8 +60,14 @@ import           Data.List                                    (foldl') import qualified Data.Text                                    as Text import           Development.IDE.Core.IdeConfiguration        (isWorkspaceFile)-import           Language.LSP.Server                          hiding-                                                              (getVirtualFile)+import qualified Development.IDE.Core.Shake                   as Shake+import           Development.IDE.Types.Logger                 (Pretty (pretty),+                                                               Priority (Info),+                                                               Recorder,+                                                               WithPriority,+                                                               cmapWithPrio,+                                                               logWith, viaShow,+                                                               (<+>)) import qualified Language.LSP.Server                          as LSP import           Language.LSP.Types                           (DidChangeWatchedFilesRegistrationOptions (DidChangeWatchedFilesRegistrationOptions),                                                                FileChangeType (FcChanged),@@ -80,29 +79,25 @@ import           Language.LSP.VFS import           System.FilePath -makeVFSHandle :: IO VFSHandle-makeVFSHandle = do-    vfsVar <- newVar (1, Map.empty)-    pure VFSHandle-        { getVirtualFile = \uri -> do-              (_nextVersion, vfs) <- readVar vfsVar-              pure $ Map.lookup uri vfs-        , setVirtualFileContents = Just $ \uri content ->-              void $ modifyVar' vfsVar $ \(nextVersion, vfs) -> (nextVersion + 1, ) $-                  case content of-                    Nothing -> Map.delete uri vfs-                    -- The second version number is only used in persistFileVFS which we do not use so we set it to 0.-                    Just content -> Map.insert uri (VirtualFile nextVersion 0 (Rope.fromText content)) vfs-        }+data Log+  = LogCouldNotIdentifyReverseDeps !NormalizedFilePath+  | LogTypeCheckingReverseDeps !NormalizedFilePath !(Maybe [NormalizedFilePath])+  | LogShake Shake.Log+  deriving Show -makeLSPVFSHandle :: LanguageContextEnv c -> VFSHandle-makeLSPVFSHandle lspEnv = VFSHandle-    { getVirtualFile = runLspT lspEnv . LSP.getVirtualFile-    , setVirtualFileContents = Nothing-   }+instance Pretty Log where+  pretty = \case+    LogCouldNotIdentifyReverseDeps path ->+      "Could not identify reverse dependencies for" <+> viaShow path+    (LogTypeCheckingReverseDeps path reverseDepPaths) ->+      "Typechecking reverse dependecies for"+      <+> viaShow path+      <> ":"+      <+> pretty (fmap (fmap show) reverseDepPaths)+    LogShake log -> pretty log -addWatchedFileRule :: (NormalizedFilePath -> Action Bool) -> Rules ()-addWatchedFileRule isWatched = defineNoDiagnostics $ \AddWatchedFile f -> do+addWatchedFileRule :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()+addWatchedFileRule recorder isWatched = defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \AddWatchedFile f -> do   isAlreadyWatched <- isWatched f   isWp <- isWorkspaceFile f   if isAlreadyWatched then pure (Just True) else@@ -114,20 +109,19 @@             Nothing -> pure $ Just False  -getModificationTimeRule :: VFSHandle -> Rules ()-getModificationTimeRule vfs = defineEarlyCutoff $ Rule $ \(GetModificationTime_ missingFileDiags) file ->-    getModificationTimeImpl vfs missingFileDiags file+getModificationTimeRule :: Recorder (WithPriority Log) -> Rules ()+getModificationTimeRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \(GetModificationTime_ missingFileDiags) file ->+    getModificationTimeImpl missingFileDiags file -getModificationTimeImpl :: VFSHandle-    -> Bool-    -> NormalizedFilePath-    -> Action-        (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion))-getModificationTimeImpl vfs missingFileDiags file = do+getModificationTimeImpl+  :: Bool+  -> NormalizedFilePath+  -> Action (Maybe BS.ByteString, ([FileDiagnostic], Maybe FileVersion))+getModificationTimeImpl missingFileDiags file = do     let file' = fromNormalizedFilePath file     let wrap time = (Just $ LBS.toStrict $ B.encode $ toRational time, ([], Just $ ModificationTime time))-    mbVirtual <- liftIO $ getVirtualFile vfs $ filePathToUri' file-    case mbVirtual of+    mbVf <- getVirtualFile file+    case mbVf of         Just (virtualFileVersion -> ver) -> do             alwaysRerun             pure (Just $ LBS.toStrict $ B.encode ver, ([], Just $ VFSVersion ver))@@ -180,43 +174,23 @@             _ -> pure ()  --- Dir.getModificationTime is surprisingly slow since it performs--- a ton of conversions. Since we do not actually care about--- the format of the time, we can get away with something cheaper.--- For now, we only try to do this on Unix systems where it seems to get the--- time spent checking file modifications (which happens on every change)--- from > 0.5s to ~0.15s.--- We might also want to try speeding this up on Windows at some point.--- TODO leverage DidChangeWatchedFile lsp notifications on clients that--- support them, as done for GetFileExists-getModTime :: FilePath -> IO POSIXTime-getModTime f =-#ifdef mingw32_HOST_OS-    utcTimeToPOSIXSeconds <$> Dir.getModificationTime f-#else-    modificationTimeHiRes <$> getFileStatus f-#endif- modificationTime :: FileVersion -> Maybe UTCTime modificationTime VFSVersion{}             = Nothing modificationTime (ModificationTime posix) = Just $ posixSecondsToUTCTime posix -getFileContentsRule :: VFSHandle -> Rules ()-getFileContentsRule vfs = define $ \GetFileContents file -> getFileContentsImpl vfs file+getFileContentsRule :: Recorder (WithPriority Log) -> Rules ()+getFileContentsRule recorder = define (cmapWithPrio LogShake recorder) $ \GetFileContents file -> getFileContentsImpl file  getFileContentsImpl-    :: VFSHandle-    -> NormalizedFilePath+    :: NormalizedFilePath     -> Action ([FileDiagnostic], Maybe (FileVersion, Maybe T.Text))-getFileContentsImpl vfs file = do+getFileContentsImpl file = do     -- need to depend on modification time to introduce a dependency with Cutoff     time <- use_ GetModificationTime file-    res <- liftIO $ ideTryIOException file $ do-        mbVirtual <- getVirtualFile vfs $ filePathToUri' file+    res <- do+        mbVirtual <- getVirtualFile file         pure $ Rope.toText . _text <$> mbVirtual-    case res of-        Left err       -> return ([err], Nothing)-        Right contents -> return ([], Just (time, contents))+    pure ([], Just (time, res))  ideTryIOException :: NormalizedFilePath -> IO a -> IO (Either FileDiagnostic a) ideTryIOException fp act =@@ -240,64 +214,57 @@             pure $ posixSecondsToUTCTime posix     return (modTime, txt) -fileStoreRules :: VFSHandle -> (NormalizedFilePath -> Action Bool) -> Rules ()-fileStoreRules vfs isWatched = do-    addIdeGlobal vfs-    getModificationTimeRule vfs-    getFileContentsRule vfs-    addWatchedFileRule isWatched+fileStoreRules :: Recorder (WithPriority Log) -> (NormalizedFilePath -> Action Bool) -> Rules ()+fileStoreRules recorder isWatched = do+    getModificationTimeRule recorder+    getFileContentsRule recorder+    addWatchedFileRule recorder isWatched  -- | Note that some buffer for a specific file has been modified but not -- with what changes.-setFileModified :: IdeState+setFileModified :: Recorder (WithPriority Log)+                -> VFSModified+                -> IdeState                 -> Bool -- ^ Was the file saved?                 -> NormalizedFilePath                 -> IO ()-setFileModified state saved nfp = do+setFileModified recorder vfs state saved nfp = do     ideOptions <- getIdeOptionsIO $ shakeExtras state     doCheckParents <- optCheckParents ideOptions     let checkParents = case doCheckParents of           AlwaysCheck -> True           CheckOnSave -> saved           _           -> False-    VFSHandle{..} <- getIdeGlobalState state-    when (isJust setVirtualFileContents) $-        fail "setFileModified can't be called on this type of VFSHandle"     join $ atomically $ recordDirtyKeys (shakeExtras state) GetModificationTime [nfp]-    restartShakeSession (shakeExtras state) (fromNormalizedFilePath nfp ++ " (modified)") []+    restartShakeSession (shakeExtras state) vfs (fromNormalizedFilePath nfp ++ " (modified)") []     when checkParents $-      typecheckParents state nfp+      typecheckParents recorder state nfp -typecheckParents :: IdeState -> NormalizedFilePath -> IO ()-typecheckParents state nfp = void $ shakeEnqueue (shakeExtras state) parents-  where parents = mkDelayedAction "ParentTC" L.Debug (typecheckParentsAction nfp)+typecheckParents :: Recorder (WithPriority Log) -> IdeState -> NormalizedFilePath -> IO ()+typecheckParents recorder state nfp = void $ shakeEnqueue (shakeExtras state) parents+  where parents = mkDelayedAction "ParentTC" L.Debug (typecheckParentsAction recorder nfp) -typecheckParentsAction :: NormalizedFilePath -> Action ()-typecheckParentsAction nfp = do+typecheckParentsAction :: Recorder (WithPriority Log) -> NormalizedFilePath -> Action ()+typecheckParentsAction recorder nfp = do     revs <- transitiveReverseDependencies nfp <$> useNoFile_ GetModuleGraph-    logger <- logger <$> getShakeExtras-    let log = L.logInfo logger . T.pack+    let log = logWith recorder     case revs of-      Nothing -> liftIO $ log $ "Could not identify reverse dependencies for " ++ show nfp+      Nothing -> log Info $ LogCouldNotIdentifyReverseDeps nfp       Just rs -> do-        liftIO $ (log $ "Typechecking reverse dependencies for " ++ show nfp ++ ": " ++ show revs)-          `catch` \(e :: SomeException) -> log (show e)+        log Info $ LogTypeCheckingReverseDeps nfp revs         void $ uses GetModIface rs  -- | Note that some keys have been modified and restart the session --   Only valid if the virtual file system was initialised by LSP, as that --   independently tracks which files are modified.-setSomethingModified :: IdeState -> [Key] -> String -> IO ()-setSomethingModified state keys reason = do-    VFSHandle{..} <- getIdeGlobalState state-    when (isJust setVirtualFileContents) $-        fail "setSomethingModified can't be called on this type of VFSHandle"+setSomethingModified :: VFSModified -> IdeState -> [Key] -> String -> IO ()+setSomethingModified vfs state keys reason = do     -- Update database to remove any files that might have been renamed/deleted     atomically $ do         writeTQueue (indexQueue $ hiedbWriter $ shakeExtras state) (\withHieDb -> withHieDb deleteMissingRealFiles)         modifyTVar' (dirtyKeys $ shakeExtras state) $ \x ->             foldl' (flip HSet.insert) x keys-    void $ restartShakeSession (shakeExtras state) reason []+    void $ restartShakeSession (shakeExtras state) vfs reason []  registerFileWatches :: [String] -> LSP.LspT Config IO Bool registerFileWatches globs = do
+ src/Development/IDE/Core/FileUtils.hs view
@@ -0,0 +1,31 @@+{-# LANGUAGE CPP          #-}++module Development.IDE.Core.FileUtils(+    getModTime,+    ) where+++import           Data.Time.Clock.POSIX+#ifdef mingw32_HOST_OS+import qualified System.Directory                             as Dir+#else+import           System.Posix.Files                           (getFileStatus,+                                                               modificationTimeHiRes)+#endif++-- Dir.getModificationTime is surprisingly slow since it performs+-- a ton of conversions. Since we do not actually care about+-- the format of the time, we can get away with something cheaper.+-- For now, we only try to do this on Unix systems where it seems to get the+-- time spent checking file modifications (which happens on every change)+-- from > 0.5s to ~0.15s.+-- We might also want to try speeding this up on Windows at some point.+-- TODO leverage DidChangeWatchedFile lsp notifications on clients that+-- support them, as done for GetFileExists+getModTime :: FilePath -> IO POSIXTime+getModTime f =+#ifdef mingw32_HOST_OS+    utcTimeToPOSIXSeconds <$> Dir.getModificationTime f+#else+    modificationTimeHiRes <$> getFileStatus f+#endif
src/Development/IDE/Core/OfInterest.hs view
@@ -14,8 +14,10 @@     deleteFileOfInterest,     setFilesOfInterest,     kick, FileOfInterestStatus(..),-    OfInterestVar(..)-    ,scheduleGarbageCollection) where+    OfInterestVar(..),+    scheduleGarbageCollection,+    Log(..)+    ) where  import           Control.Concurrent.Strict import           Control.Monad@@ -32,24 +34,37 @@ import           Data.Maybe                               (catMaybes) import           Development.IDE.Core.ProgressReporting import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Shake               hiding (Log)+import qualified Development.IDE.Core.Shake               as Shake import           Development.IDE.Plugin.Completions.Types import           Development.IDE.Types.Exports import           Development.IDE.Types.Location-import           Development.IDE.Types.Logger+import           Development.IDE.Types.Logger             (Pretty (pretty),+                                                           Recorder,+                                                           WithPriority,+                                                           cmapWithPrio,+                                                           logDebug) import           Development.IDE.Types.Options            (IdeTesting (..)) import qualified Language.LSP.Server                      as LSP import qualified Language.LSP.Types                       as LSP +data Log = LogShake Shake.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogShake log -> pretty log+ newtype OfInterestVar = OfInterestVar (Var (HashMap NormalizedFilePath FileOfInterestStatus))+ instance IsIdeGlobal OfInterestVar  -- | The rule that initialises the files of interest state.-ofInterestRules :: Rules ()-ofInterestRules = do+ofInterestRules :: Recorder (WithPriority Log) -> Rules ()+ofInterestRules recorder = do     addIdeGlobal . OfInterestVar =<< liftIO (newVar HashMap.empty)     addIdeGlobal . GarbageCollectVar =<< liftIO (newVar False)-    defineEarlyCutoff $ RuleNoDiagnostics $ \IsFileOfInterest f -> do+    defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \IsFileOfInterest f -> do         alwaysRerun         filesOfInterest <- getFilesOfInterestUntracked         let foi = maybe NotFOI IsFOI $ f `HashMap.lookup` filesOfInterest
src/Development/IDE/Core/RuleTypes.hs view
@@ -138,6 +138,9 @@     -- ^ Typechecked splice information     , tmrDeferedError    :: !Bool     -- ^ Did we defer any type errors for this module?+    , tmrRuntimeModules  :: !(ModuleEnv UTCTime)+        -- ^ Which modules did we need at runtime while compiling this file?+        -- Used for recompilation checking in the presence of TH     } instance Show TcModuleResult where     show = show . pm_mod_summary . tmrParsed@@ -158,13 +161,15 @@     -- ^ Fingerprint for the ModIface     , hirLinkableFp :: ByteString     -- ^ Fingerprint for the Linkable+    , hirRuntimeModules :: !(ModuleEnv UTCTime)+    -- ^ same as tmrRuntimeModules     }  hiFileFingerPrint :: HiFileResult -> ByteString hiFileFingerPrint HiFileResult{..} = hirIfaceFp <> hirLinkableFp -mkHiFileResult :: ModSummary -> HomeModInfo -> HiFileResult-mkHiFileResult hirModSummary hirHomeMod = HiFileResult{..}+mkHiFileResult :: ModSummary -> HomeModInfo -> ModuleEnv UTCTime -> HiFileResult+mkHiFileResult hirModSummary hirHomeMod hirRuntimeModules = HiFileResult{..}   where     hirIfaceFp = fingerprintToBS . getModuleHash . hm_iface $ hirHomeMod -- will always be two bytes     hirLinkableFp = case hm_linkable hirHomeMod of@@ -290,10 +295,12 @@ -- | Get the modification time of a file. type instance RuleResult GetModificationTime = FileVersion +-- | Either the mtime from disk or an LSP version+--   LSP versions always compare as greater than on disk versions data FileVersion-    = VFSVersion !Int32-    | ModificationTime !POSIXTime-    deriving (Show, Generic)+    = ModificationTime !POSIXTime -- order of constructors is relevant+    | VFSVersion !Int32+    deriving (Show, Generic, Eq, Ord)  instance NFData FileVersion @@ -414,7 +421,11 @@         -- Required for interactive evaluation, but leads to more cache invalidations         fullModSummary :: Bool     }-    deriving newtype (Eq, Show, Typeable, Hashable, NFData)+    deriving newtype (Eq, Typeable, Hashable, NFData)++instance Show GhcSessionDeps where+    show (GhcSessionDeps_ False) = "GhcSessionDeps"+    show (GhcSessionDeps_ True) = "GhcSessionDepsFull"  pattern GhcSessionDeps :: GhcSessionDeps pattern GhcSessionDeps = GhcSessionDeps_ False
src/Development/IDE/Core/Rules.hs view
@@ -30,7 +30,6 @@     usePropertyAction,     -- * Rules     CompiledLinkables(..),-    IsHiFileStable(..),     getParsedModuleRule,     getParsedModuleWithCommentsRule,     getLocatedImportsRule,@@ -42,7 +41,6 @@     getModIfaceFromDiskRule,     getModIfaceRule,     getModSummaryRule,-    isHiFileStableRule,     getModuleGraphRule,     knownFilesRule,     getClientSettingsRule,@@ -56,7 +54,9 @@     ghcSessionDepsDefinition,     getParsedModuleDefinition,     typeCheckRuleDefinition,+    getRebuildCount,     GhcSessionDepsConfig(..),+    Log(..),     DisplayTHWarning(..),     ) where @@ -84,6 +84,7 @@ import qualified Data.HashSet                                 as HashSet import           Data.Hashable import           Data.IORef+import           Control.Concurrent.STM.TVar import           Data.IntMap.Strict                           (IntMap) import qualified Data.IntMap.Strict                           as IntMap import           Data.List@@ -96,29 +97,29 @@ import           Data.Time                                    (UTCTime (..)) import           Data.Tuple.Extra import           Development.IDE.Core.Compile-import           Development.IDE.Core.FileExists+import           Development.IDE.Core.FileExists hiding (LogShake, Log) import           Development.IDE.Core.FileStore               (getFileContents,-                                                               modificationTime,                                                                resetInterfaceStore) import           Development.IDE.Core.IdeConfiguration-import           Development.IDE.Core.OfInterest+import           Development.IDE.Core.OfInterest hiding (LogShake, Log) import           Development.IDE.Core.PositionMapping import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Service-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Service hiding (LogShake, Log)+import           Development.IDE.Core.Shake hiding (Log)+import           Development.IDE.GHC.Compat.Env import           Development.IDE.GHC.Compat                   hiding-                                                              (parseModule,+                                                              (vcat, nest, parseModule,                                                                TargetId(..),                                                                loadInterface,-                                                               Var)-import qualified Development.IDE.GHC.Compat                   as Compat+                                                               Var,+                                                               (<+>))+import qualified Development.IDE.GHC.Compat                   as Compat hiding (vcat, nest) import qualified Development.IDE.GHC.Compat.Util              as Util import           Development.IDE.GHC.Error-import           Development.IDE.GHC.ExactPrint+import           Development.IDE.GHC.ExactPrint hiding (LogShake, Log) import           Development.IDE.GHC.Util                     hiding                                                               (modifyDynFlags) import           Development.IDE.Graph-import           Development.IDE.Graph.Classes import           Development.IDE.Import.DependencyInformation import           Development.IDE.Import.FindImports import qualified Development.IDE.Spans.AtPoint                as AtPoint@@ -127,9 +128,7 @@ import           Development.IDE.Types.Diagnostics            as Diag import           Development.IDE.Types.HscEnvEq import           Development.IDE.Types.Location-import qualified Development.IDE.Types.Logger                 as L import           Development.IDE.Types.Options-import           GHC.Generics                                 (Generic) import qualified GHC.LanguageExtensions                       as LangExt import qualified HieDb import           Ide.Plugin.Config@@ -150,7 +149,37 @@ import Language.LSP.Server (LspT) import System.Info.Extra (isWindows) import HIE.Bios.Ghc.Gap (hostIsDynamic)+import Development.IDE.Types.Logger (Recorder, logWith, cmapWithPrio, WithPriority, Pretty (pretty), (<+>), nest, vcat)+import qualified Development.IDE.Core.Shake as Shake+import qualified Development.IDE.GHC.ExactPrint as ExactPrint hiding (LogShake)+import qualified Development.IDE.Types.Logger as Logger+import qualified Development.IDE.Types.Shake as Shake +data Log+  = LogShake Shake.Log+  | LogReindexingHieFile !NormalizedFilePath+  | LogLoadingHieFile !NormalizedFilePath+  | LogLoadingHieFileFail !FilePath !SomeException+  | LogLoadingHieFileSuccess !FilePath+  | LogExactPrint ExactPrint.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogShake log -> pretty log+    LogReindexingHieFile path ->+      "Re-indexing hie file for" <+> pretty (fromNormalizedFilePath path)+    LogLoadingHieFile path ->+      "LOADING HIE FILE FOR" <+> pretty (fromNormalizedFilePath path)+    LogLoadingHieFileFail path e ->+      nest 2 $+        vcat+          [ "FAILED LOADING HIE FILE FOR" <+> pretty path+          , pretty (displayException e) ]+    LogLoadingHieFileSuccess path ->+      "SUCCEEDED LOADING HIE FILE FOR" <+> pretty path+    LogExactPrint log -> pretty log+ templateHaskellInstructions :: T.Text templateHaskellInstructions = "https://haskell-language-server.readthedocs.io/en/latest/troubleshooting.html#static-binaries" @@ -207,10 +236,10 @@ -- See https://github.com/haskell/ghcide/pull/350#discussion_r370878197 -- and https://github.com/mpickering/ghcide/pull/22#issuecomment-625070490 -- GHC wiki about: https://gitlab.haskell.org/ghc/ghc/-/wikis/api-annotations-getParsedModuleRule :: Rules ()-getParsedModuleRule =+getParsedModuleRule :: Recorder (WithPriority Log) -> Rules ()+getParsedModuleRule recorder =   -- this rule does not have early cutoff since all its dependencies already have it-  define $ \GetParsedModule file -> do+  define (cmapWithPrio LogShake recorder) $ \GetParsedModule file -> do     ModSummaryResult{msrModSummary = ms'} <- use_ GetModSummary file     sess <- use_ GhcSession file     let hsc = hscEnv sess@@ -280,11 +309,11 @@ -- | This rule provides a ParsedModule preserving all annotations, -- including keywords, punctuation and comments. -- So it is suitable for use cases where you need a perfect edit.-getParsedModuleWithCommentsRule :: Rules ()-getParsedModuleWithCommentsRule =+getParsedModuleWithCommentsRule :: Recorder (WithPriority Log) -> Rules ()+getParsedModuleWithCommentsRule recorder =   -- The parse diagnostics are owned by the GetParsedModule rule   -- For this reason, this rule does not produce any diagnostics-  defineNoDiagnostics $ \GetParsedModuleWithComments file -> do+  defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \GetParsedModuleWithComments file -> do     ModSummaryResult{msrModSummary = ms} <- use_ GetModSummary file     sess <- use_ GhcSession file     opt <- getIdeOptions@@ -315,9 +344,9 @@         Nothing   -> pure (diag, Nothing)         Just modu -> pure (diag, Just modu) -getLocatedImportsRule :: Rules ()-getLocatedImportsRule =-    define $ \GetLocatedImports file -> do+getLocatedImportsRule :: Recorder (WithPriority Log) -> Rules ()+getLocatedImportsRule recorder =+    define (cmapWithPrio LogShake recorder) $ \GetLocatedImports file -> do         ModSummaryResult{msrModSummary = ms} <- use_ GetModSummaryWithoutTimestamps file         targets <- useNoFile_ GetKnownTargets         let targetsMap = HM.mapWithKey const targets@@ -376,7 +405,7 @@ execRawDepM :: Monad m => StateT (RawDependencyInformation, IntMap a1) m a2 -> m (RawDependencyInformation, IntMap a1) execRawDepM act =     execStateT act-        ( RawDependencyInformation IntMap.empty emptyPathIdMap IntMap.empty+        ( RawDependencyInformation IntMap.empty emptyPathIdMap IntMap.empty IntMap.empty         , IntMap.empty         ) @@ -403,6 +432,11 @@           let al = modSummaryToArtifactsLocation f msum           -- Get a fresh FilePathId for the new file           fId <- getFreshFid al+          -- Record this module and its location+          whenJust msum $ \ms ->+            modifyRawDepInfo (\rd -> rd { rawModuleNameMap = IntMap.insert (getFilePathId fId)+                                                                           (ShowableModuleName (moduleName $ ms_mod ms))+                                                                           (rawModuleNameMap rd)})           -- Adding an edge to the bootmap so we can make sure to           -- insert boot nodes before the real files.           addBootMap al fId@@ -474,15 +508,15 @@     dropBootSuffix :: FilePath -> FilePath     dropBootSuffix hs_src = reverse . drop (length @[] "-boot") . reverse $ hs_src -getDependencyInformationRule :: Rules ()-getDependencyInformationRule =-    define $ \GetDependencyInformation file -> do+getDependencyInformationRule :: Recorder (WithPriority Log) -> Rules ()+getDependencyInformationRule recorder =+    define (cmapWithPrio LogShake recorder) $ \GetDependencyInformation file -> do        rawDepInfo <- rawDependencyInformation [file]        pure ([], Just $ processDependencyInformation rawDepInfo) -reportImportCyclesRule :: Rules ()-reportImportCyclesRule =-    define $ \ReportImportCycles file -> fmap (\errs -> if null errs then ([], Just ()) else (errs, Nothing)) $ do+reportImportCyclesRule :: Recorder (WithPriority Log) -> Rules ()+reportImportCyclesRule recorder =+    define (cmapWithPrio LogShake recorder) $ \ReportImportCycles file -> fmap (\errs -> if null errs then ([], Just ()) else (errs, Nothing)) $ do         DependencyInformation{..} <- use_ GetDependencyInformation file         let fileId = pathToId depPathIdMap file         case IntMap.lookup (getFilePathId fileId) depErrorNodes of@@ -514,22 +548,21 @@            pure (moduleNameString . moduleName . ms_mod $ ms)           showCycle mods  = T.intercalate ", " (map T.pack mods) -getHieAstsRule :: Rules ()-getHieAstsRule =-    define $ \GetHieAst f -> do+getHieAstsRule :: Recorder (WithPriority Log) -> Rules ()+getHieAstsRule recorder =+    define (cmapWithPrio LogShake recorder) $ \GetHieAst f -> do       tmr <- use_ TypeCheck f       hsc <- hscEnv <$> use_ GhcSessionDeps f       getHieAstRuleDefinition f hsc tmr -persistentHieFileRule :: Rules ()-persistentHieFileRule = addPersistentRule GetHieAst $ \file -> runMaybeT $ do-  res <- readHieFileForSrcFromDisk file-  vfs <- asks vfs-  (currentSource,ver) <- liftIO $ do-    mvf <- getVirtualFile vfs $ filePathToUri' file-    case mvf of-      Nothing -> (,Nothing) . T.decodeUtf8 <$> BS.readFile (fromNormalizedFilePath file)-      Just vf -> pure (Rope.toText $ _text vf, Just $ _lsp_version vf)+persistentHieFileRule :: Recorder (WithPriority Log) -> Rules ()+persistentHieFileRule recorder = addPersistentRule GetHieAst $ \file -> runMaybeT $ do+  res <- readHieFileForSrcFromDisk recorder file+  vfsRef <- asks vfsVar+  vfsData <- liftIO $ vfsMap <$> readTVarIO vfsRef+  (currentSource, ver) <- liftIO $ case M.lookup (filePathToUri' file) vfsData of+    Nothing -> (,Nothing) . T.decodeUtf8 <$> BS.readFile (fromNormalizedFilePath file)+    Just vf -> pure (Rope.toText $ _text vf, Just $ _lsp_version vf)   let refmap = Compat.generateReferencesMap . Compat.getAsts . Compat.hie_asts $ res       del = deltaFromDiff (T.decodeUtf8 $ Compat.hie_hs_src res) currentSource   pure (HAR (Compat.hie_module res) (Compat.hie_asts res) refmap mempty (HieFromDisk res),del,ver)@@ -557,8 +590,8 @@       typemap = AtPoint.computeTypeReferences . Compat.getAsts <$> masts   pure (diags <> diagsWrite, HAR (ms_mod $ tmrModSummary tmr) <$> masts <*> refmap <*> typemap <*> pure HieFresh) -getImportMapRule :: Rules ()-getImportMapRule = define $ \GetImportMap f -> do+getImportMapRule :: Recorder (WithPriority Log) -> Rules ()+getImportMapRule recorder = define (cmapWithPrio LogShake recorder) $ \GetImportMap f -> do   im <- use GetLocatedImports f   let mkImports fileImports = M.fromList $ mapMaybe (\(m, mfp) -> (unLoc m,) . artifactFilePath <$> mfp) fileImports   pure ([], ImportMap . mkImports <$> im)@@ -567,17 +600,17 @@ persistentImportMapRule :: Rules () persistentImportMapRule = addPersistentRule GetImportMap $ \_ -> pure $ Just (ImportMap mempty, idDelta, Nothing) -getBindingsRule :: Rules ()-getBindingsRule =-  define $ \GetBindings f -> do+getBindingsRule :: Recorder (WithPriority Log) -> Rules ()+getBindingsRule recorder =+  define (cmapWithPrio LogShake recorder) $ \GetBindings f -> do     HAR{hieKind=kind, refMap=rm} <- use_ GetHieAst f     case kind of       HieFresh      -> pure ([], Just $ bindings rm)       HieFromDisk _ -> pure ([], Nothing) -getDocMapRule :: Rules ()-getDocMapRule =-    define $ \GetDocMap file -> do+getDocMapRule :: Recorder (WithPriority Log) -> Rules ()+getDocMapRule recorder =+    define (cmapWithPrio LogShake recorder) $ \GetDocMap file -> do       -- Stale data for the scenario where a broken module has previously typechecked       -- but we never generated a DocMap for it       (tmrTypechecked -> tc, _) <- useWithStale_ TypeCheck file@@ -591,40 +624,39 @@ persistentDocMapRule :: Rules () persistentDocMapRule = addPersistentRule GetDocMap $ \_ -> pure $ Just (DKMap mempty mempty, idDelta, Nothing) -readHieFileForSrcFromDisk :: NormalizedFilePath -> MaybeT IdeAction Compat.HieFile-readHieFileForSrcFromDisk file = do+readHieFileForSrcFromDisk :: Recorder (WithPriority Log) -> NormalizedFilePath -> MaybeT IdeAction Compat.HieFile+readHieFileForSrcFromDisk recorder file = do   ShakeExtras{withHieDb} <- ask-  log <- asks $ L.logDebug . logger   row <- MaybeT $ liftIO $ withHieDb (\hieDb -> HieDb.lookupHieFileFromSource hieDb $ fromNormalizedFilePath file)   let hie_loc = HieDb.hieModuleHieFile row-  liftIO $ log $ "LOADING HIE FILE :" <> T.pack (show file)-  exceptToMaybeT $ readHieFileFromDisk hie_loc+  liftIO $ logWith recorder Logger.Debug $ LogLoadingHieFile file+  exceptToMaybeT $ readHieFileFromDisk recorder hie_loc -readHieFileFromDisk :: FilePath -> ExceptT SomeException IdeAction Compat.HieFile-readHieFileFromDisk hie_loc = do+readHieFileFromDisk :: Recorder (WithPriority Log) -> FilePath -> ExceptT SomeException IdeAction Compat.HieFile+readHieFileFromDisk recorder hie_loc = do   nc <- asks ideNc-  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))-                        res+  let log = (liftIO .) . logWith recorder+  case res of+    Left e -> log Logger.Debug $ LogLoadingHieFileFail hie_loc e+    Right _ -> log Logger.Debug $ LogLoadingHieFileSuccess hie_loc   except res  -- | Typechecks a module.-typeCheckRule :: Rules ()-typeCheckRule = define $ \TypeCheck file -> do+typeCheckRule :: Recorder (WithPriority Log) -> Rules ()+typeCheckRule recorder = define (cmapWithPrio LogShake recorder) $ \TypeCheck file -> do     pm <- use_ GetParsedModule file     hsc  <- hscEnv <$> use_ GhcSessionDeps file     typeCheckRuleDefinition hsc pm -knownFilesRule :: Rules ()-knownFilesRule = defineEarlyCutOffNoFile $ \GetKnownTargets -> do+knownFilesRule :: Recorder (WithPriority Log) -> Rules ()+knownFilesRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GetKnownTargets -> do   alwaysRerun   fs <- knownTargets   pure (LBS.toStrict $ B.encode $ hash fs, unhashed fs) -getModuleGraphRule :: Rules ()-getModuleGraphRule = defineNoFile $ \GetModuleGraph -> do+getModuleGraphRule :: Recorder (WithPriority Log) -> Rules ()+getModuleGraphRule recorder = defineNoFile (cmapWithPrio LogShake recorder) $ \GetModuleGraph -> do   fs <- toKnownFiles <$> useNoFile_ GetKnownTargets   rawDepInfo <- rawDependencyInformation (HashSet.toList fs)   pure $ processDependencyInformation rawDepInfo@@ -655,19 +687,16 @@  -- | Get all the linkables stored in the graph, i.e. the ones we *do not* need to unload. -- Doesn't actually contain the code, since we don't need it to unload-currentLinkables :: Action [Linkable]+currentLinkables :: Action (ModuleEnv UTCTime) currentLinkables = do     compiledLinkables <- getCompiledLinkables <$> getIdeGlobalAction-    hm <- liftIO $ readVar compiledLinkables-    pure $ map go $ moduleEnvToList hm-  where-    go (mod, time) = LM time mod []+    liftIO $ readVar compiledLinkables -loadGhcSession :: GhcSessionDepsConfig -> Rules ()-loadGhcSession ghcSessionDepsConfig = do+loadGhcSession :: Recorder (WithPriority Log) -> GhcSessionDepsConfig -> Rules ()+loadGhcSession recorder ghcSessionDepsConfig = do     -- This function should always be rerun because it tracks changes     -- to the version of the collection of HscEnv's.-    defineEarlyCutOffNoFile $ \GhcSessionIO -> do+    defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GhcSessionIO -> do         alwaysRerun         opts <- getIdeOptions         res <- optGhcSession opts@@ -675,7 +704,7 @@         let fingerprint = LBS.toStrict $ B.encode $ hash (sessionVersion res)         return (fingerprint, res) -    defineEarlyCutoff $ Rule $ \GhcSession file -> do+    defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GhcSession file -> do         IdeGhcSession{loadSessionFun} <- useNoFile_ GhcSessionIO         (val,deps) <- liftIO $ loadSessionFun $ fromNormalizedFilePath file @@ -689,18 +718,10 @@                   use_ GetModificationTime nfp         mapM_ addDependency deps -        opts <- getIdeOptions-        let cutoffHash =-              case optShakeFiles opts of-                -- optShakeFiles is only set in the DAML case.-                -- https://github.com/haskell/ghcide/pull/522#discussion_r428622915-                Just {} -> ""-                -- Hash the HscEnvEq returned so cutoff if it didn't change-                -- from last time-                Nothing -> LBS.toStrict $ B.encode (hash (snd val))+        let cutoffHash = LBS.toStrict $ B.encode (hash (snd val))         return (Just cutoffHash, val) -    defineNoDiagnostics $ \(GhcSessionDeps_ fullModSummary) file -> do+    defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \(GhcSessionDeps_ fullModSummary) file -> do         env <- use_ GhcSession file         ghcSessionDepsDefinition fullModSummary ghcSessionDepsConfig env file @@ -738,16 +759,26 @@  -- | Load a iface from disk, or generate it if there isn't one or it is out of date -- This rule also ensures that the `.hie` and `.o` (if needed) files are written out.-getModIfaceFromDiskRule :: Rules ()-getModIfaceFromDiskRule = defineEarlyCutoff $ Rule $ \GetModIfaceFromDisk f -> do+getModIfaceFromDiskRule :: Recorder (WithPriority Log) -> Rules ()+getModIfaceFromDiskRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleWithOldValue $ \GetModIfaceFromDisk f old -> do   ms <- msrModSummary <$> use_ GetModSummary f   mb_session <- use GhcSessionDeps f   case mb_session of     Nothing -> return (Nothing, ([], Nothing))     Just session -> do-      sourceModified <- use_ IsHiFileStable f       linkableType <- getLinkableType f-      r <- loadInterface (hscEnv session) ms sourceModified linkableType (regenerateHiFile session f ms)+      ver <- use_ GetModificationTime f+      let m_old = case old of+            Shake.Succeeded (Just old_version) v -> Just (v, old_version)+            Shake.Stale _   (Just old_version) v -> Just (v, old_version)+            _ -> Nothing+          recompInfo = RecompilationInfo+            { source_version = ver+            , old_value = m_old+            , get_file_version = use GetModificationTime_{missingFileDiagnostics = False}+            , regenerate = regenerateHiFile session f ms+            }+      r <- loadInterface (hscEnv session) ms linkableType recompInfo       case r of         (diags, Nothing) -> return (Nothing, (diags, Nothing))         (diags, Just x) -> do@@ -762,10 +793,10 @@ -- `.hie` file. There should be an up2date `.hie` file on -- disk since we are careful to write out the `.hie` file before writing the -- `.hi` file-getModIfaceFromDiskAndIndexRule :: Rules ()-getModIfaceFromDiskAndIndexRule =+getModIfaceFromDiskAndIndexRule :: Recorder (WithPriority Log) -> Rules ()+getModIfaceFromDiskAndIndexRule recorder =   -- doesn't need early cutoff since all its dependencies already have it-  defineNoDiagnostics $ \GetModIfaceFromDiskAndIndex f -> do+  defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \GetModIfaceFromDiskAndIndex f -> do   x <- use_ GetModIfaceFromDisk f   se@ShakeExtras{withHieDb} <- getShakeExtras @@ -787,63 +818,28 @@     -- Not in db, must re-index     _ -> do       ehf <- liftIO $ runIdeAction "GetModIfaceFromDiskAndIndex" se $ runExceptT $-        readHieFileFromDisk hie_loc+        readHieFileFromDisk recorder hie_loc       case ehf of         -- Uh oh, we failed to read the file for some reason, need to regenerate it         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.logDebug (logger se) $ "Re-indexing hie file for" <> T.pack (fromNormalizedFilePath f)+          logWith recorder Logger.Debug $ LogReindexingHieFile f           indexHieFile se ms f hash hf    return (Just x) -isHiFileStableRule :: Rules ()-isHiFileStableRule = defineEarlyCutoff $ RuleNoDiagnostics $ \IsHiFileStable f -> do-    ms <- msrModSummary <$> use_ GetModSummaryWithoutTimestamps f-    let hiFile = toNormalizedFilePath'-                $ Compat.ml_hi_file $ ms_location ms-    mbHiVersion <- use  GetModificationTime_{missingFileDiagnostics=False} hiFile-    modVersion  <- use_ GetModificationTime f-    sourceModified <- case mbHiVersion of-        Nothing -> pure SourceModified-        Just x ->-            if modificationTime x < modificationTime modVersion-                then pure SourceModified-                else do-                    fileImports <- use_ GetLocatedImports f-                    let imports = fmap artifactFilePath . snd <$> fileImports-                    deps <- uses_ IsHiFileStable (catMaybes imports)-                    pure $ if all (== SourceUnmodifiedAndStable) deps-                           then SourceUnmodifiedAndStable-                           else SourceUnmodified-    return (Just (summarize sourceModified), Just sourceModified)-  where-      summarize SourceModified            = BS.singleton 1-      summarize SourceUnmodified          = BS.singleton 2-      summarize SourceUnmodifiedAndStable = BS.singleton 3--displayTHWarning :: LspT c IO ()-displayTHWarning-  | not isWindows && not hostIsDynamic = do-      LSP.sendNotification SWindowShowMessage $-        ShowMessageParams MtInfo $ T.unwords-          [ "This HLS binary does not support Template Haskell."-          , "Follow the [instructions](" <> templateHaskellInstructions <> ")"-          , "to build an HLS binary with support for Template Haskell."-          ]-  | otherwise = return ()--newtype DisplayTHWarning = DisplayTHWarning (IO ())+newtype DisplayTHWarning = DisplayTHWarning (IO()) instance IsIdeGlobal DisplayTHWarning -getModSummaryRule :: Rules ()-getModSummaryRule = do-    env <- lspEnv <$> getShakeExtrasRules-    displayItOnce <- liftIO $ once $ LSP.runLspT (fromJust env) displayTHWarning-    addIdeGlobal (DisplayTHWarning displayItOnce)+getModSummaryRule :: LspT Config IO () -> Recorder (WithPriority Log) -> Rules ()+getModSummaryRule displayTHWarning recorder = do+    menv <- lspEnv <$> getShakeExtrasRules+    forM_ menv $ \env -> do+        displayItOnce <- liftIO $ once $ LSP.runLspT env displayTHWarning+        addIdeGlobal (DisplayTHWarning displayItOnce) -    defineEarlyCutoff $ Rule $ \GetModSummary f -> do+    defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModSummary f -> do         session' <- hscEnv <$> use_ GhcSession f         modify_dflags <- getModifyDynFlags dynFlagsModifyGlobal         let session = hscSetFlags (modify_dflags $ hsc_dflags session') session'@@ -864,7 +860,7 @@                 return ( Just (fingerprintToBS fingerPrint) , ([], Just res))             Left diags -> return (Nothing, (diags, Nothing)) -    defineEarlyCutoff $ RuleNoDiagnostics $ \GetModSummaryWithoutTimestamps f -> do+    defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \GetModSummaryWithoutTimestamps f -> do         ms <- use GetModSummary f         case ms of             Just res@ModSummaryResult{..} -> do@@ -883,12 +879,12 @@     setPriority priorityGenerateCore     liftIO $ compileModule runSimplifier packageState (tmrModSummary tm) (tmrTypechecked tm) -generateCoreRule :: Rules ()-generateCoreRule =-    define $ \GenerateCore -> generateCore (RunSimplifier True)+generateCoreRule :: Recorder (WithPriority Log) -> Rules ()+generateCoreRule recorder =+    define (cmapWithPrio LogShake recorder) $ \GenerateCore -> generateCore (RunSimplifier True) -getModIfaceRule :: Rules ()-getModIfaceRule = defineEarlyCutoff $ Rule $ \GetModIface f -> do+getModIfaceRule :: Recorder (WithPriority Log) -> Rules ()+getModIfaceRule recorder = defineEarlyCutoff (cmapWithPrio LogShake recorder) $ Rule $ \GetModIface f -> do   fileOfInterest <- use_ IsFileOfInterest f   res@(_,(_,mhmi)) <- case fileOfInterest of     IsFOI status -> do@@ -916,6 +912,20 @@       liftIO $ void $ modifyVar' compiledLinkables $ \old -> extendModuleEnv old mod time   pure res +-- | Count of total times we asked GHC to recompile+newtype RebuildCounter = RebuildCounter { getRebuildCountVar :: TVar Int }+instance IsIdeGlobal RebuildCounter++getRebuildCount :: Action Int+getRebuildCount = do+  count <- getRebuildCountVar <$> getIdeGlobalAction+  liftIO $ readTVarIO count++incrementRebuildCount :: Action ()+incrementRebuildCount = do+  count <- getRebuildCountVar <$> getIdeGlobalAction+  liftIO $ atomically $ modifyTVar' count (+1)+ -- | Also generates and indexes the `.hie` file, along with the `.o` file if needed -- Invariant maintained is that if the `.hi` file was successfully written, then the -- `.hie` and `.o` file (if needed) were also successfully written@@ -945,10 +955,10 @@               Just tmr -> do                  -- compile writes .o file-                let compile = compileModule (RunSimplifier True) hsc (pm_mod_summary pm) $ tmrTypechecked tmr+                let compile = liftIO $ compileModule (RunSimplifier True) hsc (pm_mod_summary pm) $ tmrTypechecked tmr                  -- Bang pattern is important to avoid leaking 'tmr'-                (diags'', !res) <- liftIO $ compileToObjCodeIfNeeded hsc compNeeded compile tmr+                (diags'', !res) <- compileToObjCodeIfNeeded hsc compNeeded compile tmr                  -- Write hi file                 hiDiags <- case res of@@ -972,18 +982,17 @@                     pure (hiDiags <> gDiags <> concat wDiags)                   Nothing -> pure [] -                 return (diags <> diags' <> diags'' <> hiDiags, res)  -type CompileMod m = m (IdeResult ModGuts)- -- | HscEnv should have deps included already-compileToObjCodeIfNeeded :: MonadIO m => HscEnv -> Maybe LinkableType -> CompileMod m -> TcModuleResult -> m (IdeResult HiFileResult)-compileToObjCodeIfNeeded hsc Nothing _ tmr = liftIO $ do-  res <- mkHiFileResultNoCompile hsc tmr+compileToObjCodeIfNeeded :: HscEnv -> Maybe LinkableType -> Action (IdeResult ModGuts) -> TcModuleResult -> Action (IdeResult HiFileResult)+compileToObjCodeIfNeeded hsc Nothing _ tmr = do+  incrementRebuildCount+  res <- liftIO $ mkHiFileResultNoCompile hsc tmr   pure ([], Just $! res) compileToObjCodeIfNeeded hsc (Just linkableType) getGuts tmr = do+  incrementRebuildCount   (diags, mguts) <- getGuts   case mguts of     Nothing -> pure (diags, Nothing)@@ -991,8 +1000,8 @@       (diags', !res) <- liftIO $ mkHiFileResultCompile hsc tmr guts linkableType       pure (diags++diags', res) -getClientSettingsRule :: Rules ()-getClientSettingsRule = defineEarlyCutOffNoFile $ \GetClientSettings -> do+getClientSettingsRule :: Recorder (WithPriority Log) -> Rules ()+getClientSettingsRule recorder = defineEarlyCutOffNoFile (cmapWithPrio LogShake recorder) $ \GetClientSettings -> do   alwaysRerun   settings <- clientSettings <$> getIdeConfiguration   return (LBS.toStrict $ B.encode $ hash settings, settings)@@ -1084,6 +1093,7 @@ newtype CompiledLinkables = CompiledLinkables { getCompiledLinkables :: Var (ModuleEnv UTCTime) } instance IsIdeGlobal CompiledLinkables + writeHiFileAction :: HscEnv -> HiFileResult -> Action [FileDiagnostic] writeHiFileAction hsc hiFile = do     extras <- getShakeExtras@@ -1097,33 +1107,48 @@       checkForImportCycles :: Bool     -- | Disable TH for improved performance in large codebases     , enableTemplateHaskell :: Bool+    -- | Warning to show when TH is not supported by the current HLS binary+    , templateHaskellWarning :: LspT Config IO ()     } -instance Default RulesConfig where def = RulesConfig True True+instance Default RulesConfig where+    def = RulesConfig True True displayTHWarning+      where+        displayTHWarning :: LspT c IO ()+        displayTHWarning+            | not isWindows && not hostIsDynamic = do+                LSP.sendNotification SWindowShowMessage $+                    ShowMessageParams MtInfo $ T.unwords+                    [ "This HLS binary does not support Template Haskell."+                    , "Follow the [instructions](" <> templateHaskellInstructions <> ")"+                    , "to build an HLS binary with support for Template Haskell."+                    ]+            | otherwise = return ()  -- | A rule that wires per-file rules together-mainRule :: RulesConfig -> Rules ()-mainRule RulesConfig{..} = do+mainRule :: Recorder (WithPriority Log) -> RulesConfig -> Rules ()+mainRule recorder RulesConfig{..} = do     linkables <- liftIO $ newVar emptyModuleEnv     addIdeGlobal $ CompiledLinkables linkables-    getParsedModuleRule-    getParsedModuleWithCommentsRule-    getLocatedImportsRule-    getDependencyInformationRule-    reportImportCyclesRule-    typeCheckRule-    getDocMapRule-    loadGhcSession def{checkForImportCycles}-    getModIfaceFromDiskRule-    getModIfaceFromDiskAndIndexRule-    getModIfaceRule-    getModSummaryRule-    isHiFileStableRule-    getModuleGraphRule-    knownFilesRule-    getClientSettingsRule-    getHieAstsRule-    getBindingsRule+    rebuildCountVar <- liftIO $ newTVarIO 0+    addIdeGlobal $ RebuildCounter rebuildCountVar+    getParsedModuleRule recorder+    getParsedModuleWithCommentsRule recorder+    getLocatedImportsRule recorder+    getDependencyInformationRule recorder+    reportImportCyclesRule recorder+    typeCheckRule recorder+    getDocMapRule recorder+    loadGhcSession recorder def{checkForImportCycles}+    getModIfaceFromDiskRule recorder+    getModIfaceFromDiskAndIndexRule recorder+    getModIfaceRule recorder+    getModSummaryRule templateHaskellWarning recorder+    getModuleGraphRule recorder+    knownFilesRule recorder+    getClientSettingsRule recorder+    getHieAstsRule recorder+    getBindingsRule recorder     -- This rule uses a custom newness check that relies on the encoding     --  produced by 'encodeLinkable'. This works as follows:     --   * <previous> -> <new>@@ -1131,22 +1156,12 @@     --   * Object/BCO -> NoLinkable      : the prev linkable can be ignored, signal "no change"     --   * otherwise                     : the prev linkable cannot be reused, signal "value has changed"     if enableTemplateHaskell-      then defineEarlyCutoff $ RuleWithCustomNewnessCheck (<=) $ \NeedsCompilation file ->+      then defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleWithCustomNewnessCheck (<=) $ \NeedsCompilation file ->                 needsCompilationRule file-      else defineNoDiagnostics $ \NeedsCompilation _ -> return $ Just Nothing-    generateCoreRule-    getImportMapRule-    getAnnotatedParsedSourceRule-    persistentHieFileRule+      else defineNoDiagnostics (cmapWithPrio LogShake recorder) $ \NeedsCompilation _ -> return $ Just Nothing+    generateCoreRule recorder+    getImportMapRule recorder+    getAnnotatedParsedSourceRule (cmapWithPrio LogExactPrint recorder)+    persistentHieFileRule recorder     persistentDocMapRule     persistentImportMapRule---- | Given the path to a module src file, this rule returns True if the--- corresponding `.hi` file is stable, that is, if it is newer---   than the src file, and all its dependencies are stable too.-data IsHiFileStable = IsHiFileStable-    deriving (Eq, Show, Typeable, Generic)-instance Hashable IsHiFileStable-instance NFData   IsHiFileStable--type instance RuleResult IsHiFileStable = SourceModified
src/Development/IDE/Core/Service.hs view
@@ -15,45 +15,67 @@     getDiagnostics,     ideLogger,     updatePositionMapping,+    Log(..),     ) where  import           Control.Applicative             ((<|>)) import           Development.IDE.Core.Debouncer import           Development.IDE.Core.FileExists (fileExistsRules)-import           Development.IDE.Core.OfInterest+import           Development.IDE.Core.OfInterest hiding (Log, LogShake) import           Development.IDE.Graph-import           Development.IDE.Types.Logger    as Logger+import           Development.IDE.Types.Logger    as Logger (Logger,+                                                            Pretty (pretty),+                                                            Priority (Debug),+                                                            Recorder,+                                                            WithPriority,+                                                            cmapWithPrio) import           Development.IDE.Types.Options   (IdeOptions (..)) import           Ide.Plugin.Config import qualified Language.LSP.Server             as LSP import qualified Language.LSP.Types              as LSP  import           Control.Monad-import           Development.IDE.Core.Shake+import qualified Development.IDE.Core.FileExists as FileExists+import qualified Development.IDE.Core.OfInterest as OfInterest+import           Development.IDE.Core.Shake      hiding (Log)+import qualified Development.IDE.Core.Shake      as Shake import           Development.IDE.Types.Shake     (WithHieDb) import           System.Environment              (lookupEnv)  +data Log+  = LogShake Shake.Log+  | LogOfInterest OfInterest.Log+  | LogFileExists FileExists.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogShake log      -> pretty log+    LogOfInterest log -> pretty log+    LogFileExists log -> pretty log+ ------------------------------------------------------------ -- Exposed API  -- | Initialise the Compiler Service.-initialise :: Config+initialise :: Recorder (WithPriority Log)+           -> Config            -> Rules ()            -> Maybe (LSP.LanguageContextEnv Config)            -> Logger            -> Debouncer LSP.NormalizedUri            -> IdeOptions-           -> VFSHandle            -> WithHieDb            -> IndexQueue            -> IO IdeState-initialise defaultConfig mainRule lspEnv logger debouncer options vfs withHieDb hiedbChan = do+initialise recorder defaultConfig mainRule lspEnv logger debouncer options withHieDb hiedbChan = do     shakeProfiling <- do         let fromConf = optShakeProfiling options         fromEnv <- lookupEnv "GHCIDE_BUILD_PROFILING"         return $ fromConf <|> fromEnv     shakeOpen+        (cmapWithPrio LogShake recorder)         lspEnv         defaultConfig         logger@@ -63,12 +85,11 @@         (optTesting options)         withHieDb         hiedbChan-        vfs         (optShakeOptions options)           $ do             addIdeGlobal $ GlobalIdeOptions options-            ofInterestRules-            fileExistsRules lspEnv vfs+            ofInterestRules (cmapWithPrio LogOfInterest recorder)+            fileExistsRules (cmapWithPrio LogFileExists recorder) lspEnv             mainRule  -- | Shutdown the Compiler Service.@@ -80,4 +101,4 @@ -- e.g., the ofInterestRule. runAction :: String -> IdeState -> Action a -> IO a runAction herald ide act =-  join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Info act)+  join $ shakeEnqueue (shakeExtras ide) (mkDelayedAction herald Logger.Debug act)
src/Development/IDE/Core/Shake.hs view
@@ -42,7 +42,6 @@     RuleBody(..),     define, defineNoDiagnostics,     defineEarlyCutoff,-    defineOnDisk, needOnDisk, needOnDisks,     defineNoFile, defineEarlyCutOffNoFile,     getDiagnostics,     mRunLspT, mRunLspTCallback,@@ -58,11 +57,11 @@     setPriority,     ideLogger,     actionLogger,+    getVirtualFile,     FileVersion(..),     Priority(..),     updatePositionMapping,     deleteValue, recordDirtyKeys,-    OnDiskRule(..),     WithProgressFunc, WithIndefiniteProgressFunc,     ProgressEvent(..),     DelayedAction, mkDelayedAction,@@ -73,30 +72,44 @@     IndexQueue,     HieDb,     HieDbWriter(..),-    VFSHandle(..),     addPersistentRule,     garbageCollectDirtyKeys,     garbageCollectDirtyKeysOlderThan,+    Log(..),+    VFSModified(..)     ) where  import           Control.Concurrent.Async import           Control.Concurrent.STM+import           Control.Concurrent.STM.Stats           (atomicallyNamed) import           Control.Concurrent.Strict import           Control.DeepSeq+import           Control.Exception.Extra                hiding (bracket_) import           Control.Monad.Extra import           Control.Monad.IO.Class import           Control.Monad.Reader import           Control.Monad.Trans.Maybe+import           Data.Aeson                             (toJSON) import qualified Data.ByteString.Char8                  as BS+import qualified Data.ByteString.Char8                  as BS8+import           Data.Coerce                            (coerce)+import           Data.Default import           Data.Dynamic+import           Data.EnumMap.Strict                    (EnumMap)+import qualified Data.EnumMap.Strict                    as EM+import           Data.Foldable                          (for_, toList)+import           Data.Functor                           ((<&>)) import qualified Data.HashMap.Strict                    as HMap+import           Data.HashSet                           (HashSet)+import qualified Data.HashSet                           as HSet import           Data.Hashable+import           Data.IORef import           Data.List.Extra                        (foldl', partition,                                                          takeEnd)-import           Data.Map.Strict                        (Map) import qualified Data.Map.Strict                        as Map import           Data.Maybe import qualified Data.SortedList                        as SL+import           Data.String                            (fromString) import qualified Data.Text                              as T import           Data.Time import           Data.Traversable@@ -105,7 +118,9 @@ import           Data.Unique import           Data.Vector                            (Vector) import qualified Data.Vector                            as Vector+import           Debug.Trace.Flags                      (userTracingEnabled) import           Development.IDE.Core.Debouncer+import           Development.IDE.Core.FileUtils         (getModTime) import           Development.IDE.Core.PositionMapping import           Development.IDE.Core.ProgressReporting import           Development.IDE.Core.RuleTypes@@ -121,53 +136,82 @@ import qualified Development.IDE.Graph                  as Shake import           Development.IDE.Graph.Database         (ShakeDatabase,                                                          shakeGetBuildStep,-                                                         shakeOpenDatabase,+                                                         shakeNewDatabase,                                                          shakeProfileDatabase,                                                          shakeRunDatabaseForKeys) import           Development.IDE.Graph.Rule import           Development.IDE.Types.Action import           Development.IDE.Types.Diagnostics import           Development.IDE.Types.Exports+import qualified Development.IDE.Types.Exports          as ExportsMap import           Development.IDE.Types.KnownTargets import           Development.IDE.Types.Location import           Development.IDE.Types.Logger           hiding (Priority) import qualified Development.IDE.Types.Logger           as Logger import           Development.IDE.Types.Options import           Development.IDE.Types.Shake-import           GHC.Generics+import qualified Focus+import           GHC.Fingerprint+import           HieDb.Types+import           Ide.Plugin.Config+import qualified Ide.PluginUtils                        as HLS+import           Ide.Types                              (PluginId) import           Language.LSP.Diagnostics import qualified Language.LSP.Server                    as LSP import           Language.LSP.Types import qualified Language.LSP.Types                     as LSP+import           Language.LSP.Types.Capabilities import           Language.LSP.VFS+import qualified "list-t" ListT+import           OpenTelemetry.Eventlog+import qualified StmContainers.Map                      as STM import           System.FilePath                        hiding (makeRelative)+import           System.IO.Unsafe (unsafePerformIO) import           System.Time.Extra -import           Data.IORef-import           GHC.Fingerprint-import           Language.LSP.Types.Capabilities-import           OpenTelemetry.Eventlog+data Log+  = LogCreateHieDbExportsMapStart+  | LogCreateHieDbExportsMapFinish !Int+  | LogBuildSessionRestart !String ![DelayedActionInternal] !(HashSet Key) !Seconds !(Maybe FilePath)+  | LogBuildSessionRestartTakingTooLong !Seconds+  | LogDelayedAction !(DelayedAction ()) !Seconds+  | LogBuildSessionFinish !(Maybe SomeException)+  | LogDiagsDiffButNoLspEnv ![FileDiagnostic]+  | LogDefineEarlyCutoffRuleNoDiagHasDiag !FileDiagnostic+  | LogDefineEarlyCutoffRuleCustomNewnessHasDiag !FileDiagnostic+  deriving Show -import           Control.Concurrent.STM.Stats           (atomicallyNamed)-import           Control.Exception.Extra                hiding (bracket_)-import           Data.Aeson                             (toJSON)-import qualified Data.ByteString.Char8                  as BS8-import           Data.Coerce                            (coerce)-import           Data.Default-import           Data.Foldable                          (for_, toList)-import           Data.HashSet                           (HashSet)-import qualified Data.HashSet                           as HSet-import           Data.String                            (fromString)-import           Data.Text                              (pack)-import           Debug.Trace.Flags                      (userTracingEnabled)-import qualified Development.IDE.Types.Exports          as ExportsMap-import qualified Focus-import           HieDb.Types-import           Ide.Plugin.Config-import qualified Ide.PluginUtils                        as HLS-import           Ide.Types                              (PluginId)-import qualified "list-t" ListT-import qualified StmContainers.Map                      as STM+instance Pretty Log where+  pretty = \case+    LogCreateHieDbExportsMapStart ->+      "Initializing exports map from hiedb"+    LogCreateHieDbExportsMapFinish exportsMapSize ->+      "Done initializing exports map from hiedb. Size:" <+> pretty exportsMapSize+    LogBuildSessionRestart reason actionQueue keyBackLog abortDuration shakeProfilePath ->+      vcat+        [ "Restarting build session due to" <+> pretty reason+        , "Action Queue:" <+> pretty (map actionName actionQueue)+        , "Keys:" <+> pretty (map show $ HSet.toList keyBackLog)+        , "Aborting previous build session took" <+> pretty (showDuration abortDuration) <+> pretty shakeProfilePath ]+    LogBuildSessionRestartTakingTooLong seconds ->+        "Build restart is taking too long (" <> pretty seconds <> " seconds)"+    LogDelayedAction delayedAction duration ->+      hsep+        [ "Finished:" <+> pretty (actionName delayedAction)+        , "Took:" <+> pretty (showDuration duration) ]+    LogBuildSessionFinish e ->+      vcat+        [ "Finished build session"+        , pretty (fmap displayException e) ]+    LogDiagsDiffButNoLspEnv fileDiagnostics ->+      "updateFileDiagnostics published different from new diagnostics - file diagnostics:"+      <+> pretty (showDiagnosticsColored fileDiagnostics)+    LogDefineEarlyCutoffRuleNoDiagHasDiag fileDiagnostic ->+      "defineEarlyCutoff RuleNoDiagnostics - file diagnostic:"+      <+> pretty (showDiagnosticsColored [fileDiagnostic])+    LogDefineEarlyCutoffRuleCustomNewnessHasDiag fileDiagnostic ->+      "defineEarlyCutoff RuleWithCustomNewnessCheck - file diagnostic:"+      <+> pretty (showDiagnosticsColored [fileDiagnostic])  -- | 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@@ -201,7 +245,7 @@     ,publishedDiagnostics :: STM.Map NormalizedUri [Diagnostic]     -- ^ This represents the set of diagnostics that we have published.     -- Due to debouncing not every change might get published.-    ,positionMapping :: STM.Map NormalizedUri (Map TextDocumentVersion (PositionDelta, PositionMapping))+    ,positionMapping :: STM.Map NormalizedUri (EnumMap Int32 (PositionDelta, PositionMapping))     -- ^ Map from a text document version to a PositionMapping that describes how to map     -- positions in a version of that document to positions in the latest version     -- First mapping is delta from previous version and second one is an@@ -210,7 +254,8 @@     ,ideTesting :: IdeTesting     -- ^ Whether to enable additional lsp messages used by the test suite for checking invariants     ,restartShakeSession-        :: String+        :: VFSModified+        -> String         -> [DelayedAction ()]         -> IO ()     ,ideNc :: IORef NameCache@@ -226,8 +271,12 @@     , persistentKeys :: TVar (HMap.HashMap Key GetStalePersistent)       -- ^ Registery for functions that compute/get "stale" results for the rule       -- (possibly from disk)-      -- Small and immutable after startup, so not worth using an STM.Map.-    , vfs :: VFSHandle+    , vfsVar :: TVar VFS+    -- ^ A snapshot of the current state of the virtual file system. Updated on shakeRestart+    -- VFS state is managed by LSP. However, the state according to the lsp library may be newer than the state of the current session,+    -- leaving us vulnerable to suble race conditions. To avoid this, we take a snapshot of the state of the VFS on every+    -- restart, so that the whole session sees a single consistent view of the VFS.+    -- We don't need a STM.Map because we never update individual keys ourselves.     , defaultConfig :: Config       -- ^ Default HLS config, only relevant if the client does not provide any Config     , dirtyKeys :: TVar (HashSet Key)@@ -268,19 +317,18 @@  class Typeable a => IsIdeGlobal a where +-- | Read a virtual file from the current snapshot+getVirtualFile :: NormalizedFilePath -> Action (Maybe VirtualFile)+getVirtualFile nf = do+  vfs <- fmap vfsMap . liftIO . readTVarIO . vfsVar =<< getShakeExtras+  pure $! Map.lookup (filePathToUri' nf) vfs -- Don't leak a reference to the entire map --- | haskell-lsp manages the VFS internally and automatically so we cannot use--- the builtin VFS without spawning up an LSP server. To be able to test things--- like `setBufferModified` we abstract over the VFS implementation.-data VFSHandle = VFSHandle-    { getVirtualFile         :: NormalizedUri -> IO (Maybe VirtualFile)-        -- ^ get the contents of a virtual file-    , setVirtualFileContents :: Maybe (NormalizedUri -> Maybe T.Text -> IO ())-        -- ^ set a specific file to a value. If Nothing then we are ignoring these-        --   signals anyway so can just say something was modified-    }-instance IsIdeGlobal VFSHandle+-- Take a snapshot of the current LSP VFS+vfsSnapshot :: Maybe (LSP.LanguageContextEnv a) -> IO VFS+vfsSnapshot Nothing       = pure $ VFS mempty ""+vfsSnapshot (Just lspEnv) = LSP.runLspT lspEnv LSP.getVirtualFiles + addIdeGlobal :: IsIdeGlobal a => a -> Rules () addIdeGlobal x = do     extras <- getShakeExtrasRules@@ -292,7 +340,6 @@         Just _ -> error $ "Internal error, addIdeGlobalExtras, got the same type twice for " ++ show ty         Nothing -> HMap.insert ty (toDyn x) mp - getIdeGlobalExtras :: forall a . IsIdeGlobal a => ShakeExtras -> IO a getIdeGlobalExtras ShakeExtras{globals} = do     let typ = typeRep (Proxy :: Proxy a)@@ -345,13 +392,18 @@             f <- MaybeT $ pure $ HMap.lookup (Key k) pmap             (dv,del,ver) <- MaybeT $ runIdeAction "lastValueIO" s $ f file             MaybeT $ pure $ (,del,ver) <$> fromDynamic dv-          atomicallyNamed "lastValueIO" $ case mv of-            Nothing -> do+          case mv of+            Nothing -> atomicallyNamed "lastValueIO 1" $ do                 STM.focus (Focus.alter (alterValue $ Failed True)) (toKey k file) state                 return Nothing             Just (v,del,ver) -> do-                STM.focus (Focus.alter (alterValue $ Stale (Just del) ver (toDyn v))) (toKey k file) state-                Just . (v,) . addDelta del <$> mappingForVersion positionMapping file ver+                actual_version <- case ver of+                  Just ver -> pure (Just $ VFSVersion ver)+                  Nothing -> (Just . ModificationTime <$> getModTime (fromNormalizedFilePath file))+                              `catch` (\(_ :: IOException) -> pure Nothing)+                atomicallyNamed "lastValueIO 2" $ do+                  STM.focus (Focus.alter (alterValue $ Stale (Just del) actual_version (toDyn v))) (toKey k file) state+                  Just . (v,) . addDelta del <$> mappingForVersion positionMapping file actual_version          -- We got a new stale value from the persistent rule, insert it in the map without affecting diagnostics         alterValue new Nothing = Just (ValueWithDiagnostics new mempty) -- If it wasn't in the map, give it empty diagnostics@@ -379,13 +431,14 @@     liftIO $ lastValueIO s key file  mappingForVersion-    :: STM.Map NormalizedUri (Map TextDocumentVersion (a, PositionMapping))+    :: STM.Map NormalizedUri (EnumMap Int32 (a, PositionMapping))     -> NormalizedFilePath-    -> TextDocumentVersion+    -> Maybe FileVersion     -> STM PositionMapping-mappingForVersion allMappings file ver = do+mappingForVersion allMappings file (Just (VFSVersion ver)) = do     mapping <- STM.lookup (filePathToUri' file) allMappings-    return $ maybe zeroMapping snd $ Map.lookup ver =<< mapping+    return $ maybe zeroMapping snd $ EM.lookup ver =<< mapping+mappingForVersion _ _ _ = pure zeroMapping  type IdeRule k v =   ( Shake.RuleResult k ~ v@@ -407,7 +460,6 @@ data IdeState = IdeState     {shakeDb              :: ShakeDatabase     ,shakeSession         :: MVar ShakeSession-    ,shakeClose           :: IO ()     ,shakeExtras          :: ShakeExtras     ,shakeDatabaseProfile :: ShakeDatabase -> IO (Maybe FilePath)     }@@ -494,7 +546,8 @@     Failed _        -> val  -- | Open a 'IdeState', should be shut using 'shakeShut'.-shakeOpen :: Maybe (LSP.LanguageContextEnv Config)+shakeOpen :: Recorder (WithPriority Log)+          -> Maybe (LSP.LanguageContextEnv Config)           -> Config           -> Logger           -> Debouncer NormalizedUri@@ -503,12 +556,13 @@           -> IdeTesting           -> WithHieDb           -> IndexQueue-          -> VFSHandle           -> ShakeOptions           -> Rules ()           -> IO IdeState-shakeOpen lspEnv defaultConfig logger debouncer-  shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) withHieDb indexQueue vfs opts rules = mdo+shakeOpen recorder lspEnv defaultConfig logger debouncer+  shakeProfileDir (IdeReportProgress reportProgress) ideTesting@(IdeTesting testing) withHieDb indexQueue opts rules = mdo+    let log :: Logger.Priority -> Log -> IO ()+        log = logWith recorder      us <- mkSplitUniqSupply 'r'     ideNc <- newIORef (initNameCache us knownKeyNames)@@ -520,7 +574,7 @@         publishedDiagnostics <- STM.newIO         positionMapping <- STM.newIO         knownTargetsVar <- newTVarIO $ hashed HMap.empty-        let restartShakeSession = shakeRestart ideState+        let restartShakeSession = shakeRestart recorder ideState         persistentKeys <- newTVarIO HMap.empty         indexPending <- newTVarIO HMap.empty         indexCompleted <- newTVarIO 0@@ -528,11 +582,12 @@         let hiedbWriter = HieDbWriter{..}         exportsMap <- newTVarIO mempty         -- lazily initialize the exports map with the contents of the hiedb+        -- TODO: exceptions can be swallowed here?         _ <- async $ do-            logDebug logger "Initializing exports map from hiedb"+            log Debug LogCreateHieDbExportsMapStart             em <- createExportsMapHieDb withHieDb             atomically $ modifyTVar' exportsMap (<> em)-            logDebug logger $ "Done initializing exports map from hiedb (" <> pack(show (ExportsMap.size em)) <> ")"+            log Debug $ LogCreateHieDbExportsMapFinish (ExportsMap.size em)          progress <- do             let (before, after) = if testing then (0,0.1) else (0.1,0.1)@@ -544,12 +599,13 @@         let clientCapabilities = maybe def LSP.resClientCapabilities lspEnv          dirtyKeys <- newTVarIO mempty+        -- Take one VFS snapshot at the start+        vfsVar <- newTVarIO =<< vfsSnapshot lspEnv         pure ShakeExtras{..}-    (shakeDbM, shakeClose) <--        shakeOpenDatabase+    shakeDb  <-+        shakeNewDatabase             opts { shakeExtra = newShakeExtra shakeExtras }             rules-    shakeDb <- shakeDbM     shakeSession <- newEmptyMVar     shakeDatabaseProfile <- shakeDatabaseProfileIO shakeProfileDir     let ideState = IdeState{..}@@ -584,9 +640,12 @@   -- | Must be called in the 'Initialized' handler and only once-shakeSessionInit :: IdeState -> IO ()-shakeSessionInit ide@IdeState{..} = do-    initSession <- newSession shakeExtras shakeDb [] "shakeSessionInit"+shakeSessionInit :: Recorder (WithPriority Log) -> IdeState -> IO ()+shakeSessionInit recorder ide@IdeState{..} = do+    -- Take a snapshot of the VFS - it should be empty as we've recieved no notifications+    -- till now, but it can't hurt to be in sync with the `lsp` library.+    vfs <- vfsSnapshot (lspEnv shakeExtras)+    initSession <- newSession recorder shakeExtras (VFSModified vfs) shakeDb [] "shakeSessionInit"     putMVar shakeSession initSession     logDebug (ideLogger ide) "Shake session initialized" @@ -597,7 +656,6 @@     -- request so we first abort that.     for_ runner cancelShakeSession     void $ shakeDatabaseProfile shakeDb-    shakeClose     progressStop $ progress shakeExtras  @@ -626,31 +684,40 @@ -- | Restart the current 'ShakeSession' with the given system actions. --   Any actions running in the current session will be aborted, --   but actions added via 'shakeEnqueue' will be requeued.-shakeRestart :: IdeState -> String -> [DelayedAction ()] -> IO ()-shakeRestart IdeState{..} reason acts =+shakeRestart :: Recorder (WithPriority Log) -> IdeState -> VFSModified -> String -> [DelayedAction ()] -> IO ()+shakeRestart recorder IdeState{..} vfs reason acts =     withMVar'         shakeSession         (\runner -> do-              (stopTime,()) <- duration (cancelShakeSession runner)+              let log = logWith recorder+              (stopTime,()) <- duration $ logErrorAfter 10 recorder $ cancelShakeSession runner               res <- shakeDatabaseProfile shakeDb               backlog <- readTVarIO $ dirtyKeys shakeExtras               queue <- atomicallyNamed "actionQueue - peek" $ peekInProgress $ actionQueue shakeExtras++              log Debug $ LogBuildSessionRestart reason queue backlog stopTime res+               let profile = case res of                       Just fp -> ", profile saved at " <> fp                       _       -> ""+              -- TODO: should replace with logging using a logger that sends lsp message               let msg = T.pack $ "Restarting build session " ++ reason' ++ queueMsg ++ keysMsg ++ abortMsg                   reason' = "due to " ++ reason                   queueMsg = " with queue " ++ show (map actionName queue)                   keysMsg = " for keys " ++ show (HSet.toList backlog) ++ " "                   abortMsg = "(aborting the previous one took " ++ showDuration stopTime ++ profile ++ ")"-              logDebug (logger shakeExtras) msg               notifyTestingLogMessage shakeExtras msg         )         -- It is crucial to be masked here, otherwise we can get killed         -- between spawning the new thread and updating shakeSession.         -- See https://github.com/haskell/ghcide/issues/79         (\() -> do-          (,()) <$> newSession shakeExtras shakeDb acts reason)+          (,()) <$> newSession recorder shakeExtras vfs shakeDb acts reason)+    where+        logErrorAfter :: Seconds -> Recorder (WithPriority Log) -> IO () -> IO ()+        logErrorAfter seconds recorder action = flip withAsync (const action) $ do+            sleep seconds+            logWith recorder Error (LogBuildSessionRestartTakingTooLong seconds)  notifyTestingLogMessage :: ShakeExtras -> T.Text -> IO () notifyTestingLogMessage extras msg = do@@ -681,15 +748,25 @@               ]     return (wait' b >>= either throwIO return) +data VFSModified = VFSUnmodified | VFSModified !VFS+ -- | Set up a new 'ShakeSession' with a set of initial actions --   Will crash if there is an existing 'ShakeSession' running. newSession-    :: ShakeExtras+    :: Recorder (WithPriority Log)+    -> ShakeExtras+    -> VFSModified     -> ShakeDatabase     -> [DelayedActionInternal]     -> String     -> IO ShakeSession-newSession extras@ShakeExtras{..} shakeDb acts reason = do+newSession recorder extras@ShakeExtras{..} vfsMod shakeDb acts reason = do++    -- Take a new VFS snapshot+    case vfsMod of+      VFSUnmodified -> pure ()+      VFSModified vfs -> atomically $ writeTVar vfsVar vfs+     IdeOptions{optRunSubset} <- getIdeOptionsIO extras     reenqueued <- atomicallyNamed "actionQueue - peek" $ peekInProgress actionQueue     allPendingKeys <-@@ -709,10 +786,7 @@             getAction d             liftIO $ atomicallyNamed "actionQueue - done" $ doneQueue d actionQueue             runTime <- liftIO start-            let msg = T.pack $ "finish: " ++ actionName d-                            ++ " (took " ++ showDuration runTime ++ ")"-            liftIO $ do-                logPriority logger (actionPriority d) msg+            logWith recorder (actionPriority d) $ LogDelayedAction d runTime          -- The inferred type signature doesn't work in ghc >= 9.0.1         workRun :: (forall b. IO b -> IO b) -> IO (IO ())@@ -728,7 +802,11 @@                       Right _ -> "completed"           let msg = T.pack $ "Finishing build session(" ++ res' ++ ")"           return $ do-              logDebug logger msg+              let exception =+                    case res of+                      Left e -> Just e+                      _      -> Nothing+              logWith recorder Debug $ LogBuildSessionFinish exception               notifyTestingLogMessage extras msg      -- Do the work in a background thread@@ -736,6 +814,7 @@      -- run the wrap up in a separate thread since it contains interruptible     -- commands (and we are not using uninterruptible mask)+    -- TODO: can possibly swallow exceptions?     _ <- async $ join $ wait workThread      --  Cancelling is required to flush the Shake database when either@@ -843,13 +922,13 @@ -- | Define a new Rule without early cutoff define     :: IdeRule k v-    => (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()-define op = defineEarlyCutoff $ Rule $ \k v -> (Nothing,) <$> op k v+    => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (IdeResult v)) -> Rules ()+define recorder op = defineEarlyCutoff recorder $ Rule $ \k v -> (Nothing,) <$> op k v  defineNoDiagnostics     :: IdeRule k v-    => (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules ()-defineNoDiagnostics op = defineEarlyCutoff $ RuleNoDiagnostics $ \k v -> (Nothing,) <$> op k v+    => Recorder (WithPriority Log) -> (k -> NormalizedFilePath -> Action (Maybe v)) -> Rules ()+defineNoDiagnostics recorder op = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k v -> (Nothing,) <$> op k v  -- | Request a Rule result if available use :: IdeRule k v@@ -960,6 +1039,11 @@     -- whether the rule succeeded or not.     mapM (lastValue key) files +useWithoutDependency :: IdeRule k v+    => k -> NormalizedFilePath -> Action (Maybe v)+useWithoutDependency key file =+    (\[A value] -> currentValue value) <$> applyWithoutDependency [Q (key, file)]+ data RuleBody k v   = Rule (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, IdeResult v))   | RuleNoDiagnostics (k -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe v))@@ -967,54 +1051,60 @@     { newnessCheck :: BS.ByteString -> BS.ByteString -> Bool     , build :: k -> NormalizedFilePath -> Action (Maybe BS.ByteString, Maybe v)     }+  | RuleWithOldValue (k -> NormalizedFilePath -> Value v -> Action (Maybe BS.ByteString, IdeResult v))  -- | Define a new Rule with early cutoff defineEarlyCutoff     :: IdeRule k v-    => RuleBody k v+    => Recorder (WithPriority Log)+    -> RuleBody k v     -> Rules ()-defineEarlyCutoff (Rule op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do+defineEarlyCutoff recorder (Rule op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do     extras <- getShakeExtras-    let diagnostics diags = do+    let diagnostics ver diags = do             traceDiagnostics diags-            updateFileDiagnostics file (Key key) extras . map (\(_,y,z) -> (y,z)) $ diags-    defineEarlyCutoff' diagnostics (==) key file old mode $ op key file-defineEarlyCutoff (RuleNoDiagnostics op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do-    ShakeExtras{logger} <- getShakeExtras-    let diagnostics diags = do+            updateFileDiagnostics recorder file ver (Key key) extras . map (\(_,y,z) -> (y,z)) $ diags+    defineEarlyCutoff' diagnostics (==) key file old mode $ const $ op key file+defineEarlyCutoff recorder (RuleNoDiagnostics op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do+    let diagnostics _ver diags = do             traceDiagnostics diags-            mapM_ (\d -> liftIO $ logWarning logger $ showDiagnosticsColored [d]) diags-    defineEarlyCutoff' diagnostics (==) key file old mode $ second (mempty,) <$> op key file-defineEarlyCutoff RuleWithCustomNewnessCheck{..} =+            mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleNoDiagHasDiag) diags+    defineEarlyCutoff' diagnostics (==) key file old mode $ const $ second (mempty,) <$> op key file+defineEarlyCutoff recorder RuleWithCustomNewnessCheck{..} =     addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode ->         otTracedAction key file mode traceA $ \ traceDiagnostics -> do-            ShakeExtras{logger} <- getShakeExtras-            let diagnostics diags = do-                    mapM_ (\d -> liftIO $ logWarning logger $ showDiagnosticsColored [d]) diags+            let diagnostics _ver diags = do                     traceDiagnostics diags+                    mapM_ (logWith recorder Warning . LogDefineEarlyCutoffRuleCustomNewnessHasDiag) diags             defineEarlyCutoff' diagnostics newnessCheck key file old mode $-                second (mempty,) <$> build key file+                const $ second (mempty,) <$> build key file+defineEarlyCutoff recorder (RuleWithOldValue op) = addRule $ \(Q (key, file)) (old :: Maybe BS.ByteString) mode -> otTracedAction key file mode traceA $ \traceDiagnostics -> do+    extras <- getShakeExtras+    let diagnostics ver diags = do+            traceDiagnostics diags+            updateFileDiagnostics recorder file ver (Key key) extras . map (\(_,y,z) -> (y,z)) $ diags+    defineEarlyCutoff' diagnostics (==) key file old mode $ op key file -defineNoFile :: IdeRule k v => (k -> Action v) -> Rules ()-defineNoFile f = defineNoDiagnostics $ \k file -> do+defineNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action v) -> Rules ()+defineNoFile recorder f = defineNoDiagnostics recorder $ \k file -> do     if file == emptyFilePath then do res <- f k; return (Just res) else         fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file" -defineEarlyCutOffNoFile :: IdeRule k v => (k -> Action (BS.ByteString, v)) -> Rules ()-defineEarlyCutOffNoFile f = defineEarlyCutoff $ RuleNoDiagnostics $ \k file -> do+defineEarlyCutOffNoFile :: IdeRule k v => Recorder (WithPriority Log) -> (k -> Action (BS.ByteString, v)) -> Rules ()+defineEarlyCutOffNoFile recorder f = defineEarlyCutoff recorder $ RuleNoDiagnostics $ \k file -> do     if file == emptyFilePath then do (hash, res) <- f k; return (Just hash, Just res) else         fail $ "Rule " ++ show k ++ " should always be called with the empty string for a file"  defineEarlyCutoff'-    :: IdeRule k v-    => ([FileDiagnostic] -> Action ()) -- ^ update diagnostics+    :: forall k v. IdeRule k v+    => (TextDocumentVersion -> [FileDiagnostic] -> Action ()) -- ^ update diagnostics     -- | compare current and previous for freshness     -> (BS.ByteString -> BS.ByteString -> Bool)     -> k     -> NormalizedFilePath     -> Maybe BS.ByteString     -> RunMode-    -> Action (Maybe BS.ByteString, IdeResult v)+    -> (Value v -> Action (Maybe BS.ByteString, IdeResult v))     -> Action (RunResult (A (RuleResult k))) defineEarlyCutoff' doDiagnostics cmp key file old mode action = do     ShakeExtras{state, progress, dirtyKeys} <- getShakeExtras@@ -1026,8 +1116,9 @@                 case v of                     -- No changes in the dependencies and we have                     -- an existing successful result.-                    Just (v@Succeeded{}, diags) -> do-                        doDiagnostics $ Vector.toList diags+                    Just (v@(Succeeded _ x), diags) -> do+                        ver <- estimateFileVersionUnsafely key (Just x) file+                        doDiagnostics (vfsVersion =<< ver) $ Vector.toList diags                         return $ Just $ RunResult ChangedNothing old $ A v                     _ -> return Nothing             _ ->@@ -1037,26 +1128,23 @@         res <- case val of             Just res -> return res             Nothing -> do+                staleV <- liftIO $ atomicallyNamed "define -read 3" $ getValues state key file <&> \case+                    Nothing                   -> Failed False+                    Just (Succeeded ver v, _) -> Stale Nothing ver v+                    Just (Stale d ver v, _)   -> Stale d ver v+                    Just (Failed b, _)        -> Failed b                 (bs, (diags, res)) <- actionCatch-                    (do v <- action; liftIO $ evaluate $ force v) $+                    (do v <- action staleV; liftIO $ evaluate $ force v) $                     \(e :: SomeException) -> do                         pure (Nothing, ([ideErrorText file $ T.pack $ show e | not $ isBadDependency e],Nothing))-                modTime <- liftIO $ (currentValue . fst =<<) <$> atomicallyNamed "define - read 2" (getValues state GetModificationTime file)++                ver <- estimateFileVersionUnsafely key res file                 (bs, res) <- case res of                     Nothing -> do-                        staleV <- liftIO $ atomicallyNamed "define -read 3" $ getValues state key file-                        pure $ case staleV of-                            Nothing -> (toShakeValue ShakeResult bs, Failed False)-                            Just v -> case v of-                                (Succeeded ver v, _) ->-                                    (toShakeValue ShakeStale bs, Stale Nothing ver v)-                                (Stale d ver v, _) ->-                                    (toShakeValue ShakeStale bs, Stale d ver v)-                                (Failed b, _) ->-                                    (toShakeValue ShakeResult bs, Failed b)-                    Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded (vfsVersion =<< modTime) v)+                        pure (toShakeValue ShakeStale bs, staleV)+                    Just v -> pure (maybe ShakeNoCutoff ShakeResult bs, Succeeded ver v)                 liftIO $ atomicallyNamed "define - write" $ setValues state key file res (Vector.fromList diags)-                doDiagnostics diags+                doDiagnostics (vfsVersion =<< ver) diags                 let eq = case (bs, fmap decodeShakeValue old) of                         (ShakeResult a, Just (ShakeResult b)) -> cmp a b                         (ShakeStale a, Just (ShakeStale b))   -> cmp a b@@ -1069,115 +1157,73 @@                     A res         liftIO $ atomicallyNamed "define - dirtyKeys" $ modifyTVar' dirtyKeys (HSet.delete $ toKey key file)         return res+  where+    -- Highly unsafe helper to compute the version of a file+    -- without creating a dependency on the GetModificationTime rule+    -- (and without creating cycles in the build graph).+    estimateFileVersionUnsafely+        :: forall k v+         . IdeRule k v+        => k+        -> Maybe v+        -> NormalizedFilePath+        -> Action (Maybe FileVersion)+    estimateFileVersionUnsafely _k v fp+        | fp == emptyFilePath = pure Nothing+        | Just Refl <- eqT @k @GetModificationTime = pure v+        -- GetModificationTime depends on these rules, so avoid creating a cycle+        | Just Refl <- eqT @k @AddWatchedFile = pure Nothing+        | Just Refl <- eqT @k @IsFileOfInterest = pure Nothing+        -- GetFileExists gets called for missing files+        | Just Refl <- eqT @k @GetFileExists = pure Nothing+        -- For all other rules - compute the version properly without:+        --  * creating a dependency: If everything depends on GetModificationTime, we lose early cutoff+        --  * creating bogus "file does not exists" diagnostics+        | otherwise = useWithoutDependency (GetModificationTime_ False) fp  traceA :: A v -> String traceA (A Failed{})    = "Failed" traceA (A Stale{})     = "Stale" traceA (A Succeeded{}) = "Success" --- | Rule type, input file-data QDisk k = QDisk k NormalizedFilePath-  deriving (Eq, Generic)--instance Hashable k => Hashable (QDisk k)--instance NFData k => NFData (QDisk k)--instance Show k => Show (QDisk k) where-    show (QDisk k file) =-        show k ++ "; " ++ fromNormalizedFilePath file--type instance RuleResult (QDisk k) = Bool--data OnDiskRule = OnDiskRule-  { getHash :: Action BS.ByteString-  -- This is used to figure out if the state on disk corresponds to the state in the Shake-  -- database and we can therefore avoid rerunning. Often this can just be the file hash but-  -- in some cases we can be more aggressive, e.g., for GHC interface files this can be the ABI hash which-  -- is more stable than the hash of the interface file.-  -- An empty bytestring indicates that the state on disk is invalid, e.g., files are missing.-  -- We do not use a Maybe since we have to deal with encoding things into a ByteString anyway in the Shake DB.-  , runRule :: Action (IdeResult BS.ByteString)-  -- The actual rule code which produces the new hash (or Nothing if the rule failed) and the diagnostics.-  }---- This is used by the DAML compiler for incremental builds. Right now this is not used by--- ghcide itself but that might change in the future.--- The reason why this code lives in ghcide and in particular in this module is that it depends quite heavily on--- the internals of this module that we do not want to expose.-defineOnDisk-  :: (Shake.ShakeValue k, RuleResult k ~ ())-  => (k -> NormalizedFilePath -> OnDiskRule)-  -> Rules ()-defineOnDisk act = addRule $-  \(QDisk key file) (mbOld :: Maybe BS.ByteString) mode -> do-      extras <- getShakeExtras-      let OnDiskRule{..} = act key file-      let validateHash h-              | BS.null h = Nothing-              | otherwise = Just h-      let runAct = actionCatch runRule $-              \(e :: SomeException) -> pure ([ideErrorText file $ T.pack $ displayException e | not $ isBadDependency e], Nothing)-      case mbOld of-          Nothing -> do-              (diags, mbHash) <- runAct-              updateFileDiagnostics file (Key key) extras $ map (\(_,y,z) -> (y,z)) diags-              pure $ RunResult ChangedRecomputeDiff (fromMaybe "" mbHash) (isJust mbHash)-          Just old -> do-              current <- validateHash <$> (actionCatch getHash $ \(_ :: SomeException) -> pure "")-              if mode == RunDependenciesSame && Just old == current && not (BS.null old)-                  then-                    -- None of our dependencies changed, we’ve had a successful run before and-                    -- the state on disk matches the state in the Shake database.-                    pure $ RunResult ChangedNothing (fromMaybe "" current) (isJust current)-                  else do-                    (diags, mbHash) <- runAct-                    updateFileDiagnostics file (Key key) extras $ map (\(_,y,z) -> (y,z)) diags-                    let change-                          | mbHash == Just old = ChangedRecomputeSame-                          | otherwise = ChangedRecomputeDiff-                    pure $ RunResult change (fromMaybe "" mbHash) (isJust mbHash)--needOnDisk :: (Shake.ShakeValue k, RuleResult k ~ ()) => k -> NormalizedFilePath -> Action ()-needOnDisk k file = do-    successfull <- apply1 (QDisk k file)-    liftIO $ unless successfull $ throwIO $ BadDependency (show k)--needOnDisks :: (Shake.ShakeValue k, RuleResult k ~ ()) => k -> [NormalizedFilePath] -> Action ()-needOnDisks k files = do-    successfulls <- apply $ map (QDisk k) files-    liftIO $ unless (and successfulls) $ throwIO $ BadDependency (show k)- updateFileDiagnostics :: MonadIO m-  => NormalizedFilePath+  => Recorder (WithPriority Log)+  -> NormalizedFilePath+  -> TextDocumentVersion   -> Key   -> ShakeExtras   -> [(ShowDiagnostic,Diagnostic)] -- ^ current results   -> m ()-updateFileDiagnostics fp k ShakeExtras{logger, diagnostics, hiddenDiagnostics, publishedDiagnostics, state, debouncer, lspEnv} current = liftIO $ do-    modTime <- (currentValue . fst =<<) <$> atomicallyNamed "diagnostics - read" (getValues state GetModificationTime fp)+updateFileDiagnostics recorder fp ver k ShakeExtras{diagnostics, hiddenDiagnostics, publishedDiagnostics, debouncer, lspEnv} current =+  liftIO $ withTrace ("update diagnostics " <> fromString(fromNormalizedFilePath fp)) $ \ addTag -> do+    addTag "key" (show k)     let (currentShown, currentHidden) = partition ((== ShowDiag) . fst) current         uri = filePathToUri' fp-        ver = vfsVersion =<< modTime-        update new store = setStageDiagnostics uri ver (T.pack $ show k) new store+        addTagUnsafe :: String -> String -> String -> a -> a+        addTagUnsafe msg t x v = unsafePerformIO(addTag (msg <> t) x) `seq` v+        update :: (forall a. String -> String -> a -> a) -> [Diagnostic] -> STMDiagnosticStore -> STM [Diagnostic]+        update addTagUnsafe new store = addTagUnsafe "count" (show $ Prelude.length new) $ setStageDiagnostics addTagUnsafe uri ver (T.pack $ show k) new store+    addTag "version" (show ver)     mask_ $ do         -- Mask async exceptions to ensure that updated diagnostics are always         -- published. Otherwise, we might never publish certain diagnostics if         -- an exception strikes between modifyVar but before         -- publishDiagnosticsNotification.-        newDiags <- liftIO $ atomicallyNamed "diagnostics - update" $ update (map snd currentShown) diagnostics-        _ <- liftIO $ atomicallyNamed "diagnostics - hidden" $ update (map snd currentHidden) hiddenDiagnostics+        newDiags <- liftIO $ atomicallyNamed "diagnostics - update" $ update (addTagUnsafe "shown ") (map snd currentShown) diagnostics+        _ <- liftIO $ atomicallyNamed "diagnostics - hidden" $ update (addTagUnsafe "hidden ") (map snd currentHidden) hiddenDiagnostics         let uri = filePathToUri' fp         let delay = if null newDiags then 0.1 else 0-        registerEvent debouncer delay uri $ do+        registerEvent debouncer delay uri $ withTrace ("report diagnostics " <> fromString (fromNormalizedFilePath fp)) $ \tag -> do              join $ mask_ $ do                  lastPublish <- atomicallyNamed "diagnostics - publish" $ STM.focus (Focus.lookupWithDefault [] <* Focus.insert newDiags) uri publishedDiagnostics                  let action = when (lastPublish /= newDiags) $ case lspEnv of                         Nothing -> -- Print an LSP event.-                            logInfo logger $ showDiagnosticsColored $ map (fp,ShowDiag,) newDiags-                        Just env -> LSP.runLspT env $+                            logWith recorder Info $ LogDiagsDiffButNoLspEnv (map (fp, ShowDiag,) newDiags)+                        Just env -> LSP.runLspT env $ do+                            liftIO $ tag "count" (show $ Prelude.length newDiags)+                            liftIO $ tag "key" (show k)                             LSP.sendNotification LSP.STextDocumentPublishDiagnostics $-                            LSP.PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)+                                LSP.PublishDiagnosticsParams (fromNormalizedUri uri) (fmap fromIntegral ver) (List newDiags)                  return action  newtype Priority = Priority Double@@ -1199,28 +1245,35 @@ getDiagnosticsFromStore :: StoreItem -> [Diagnostic] getDiagnosticsFromStore (StoreItem _ diags) = concatMap SL.fromSortedList $ Map.elems diags -updateSTMDiagnostics :: STMDiagnosticStore-                  -> NormalizedUri -> TextDocumentVersion -> DiagnosticsBySource-                  -> STM [LSP.Diagnostic]-updateSTMDiagnostics store uri mv newDiagsBySource =+updateSTMDiagnostics ::+  (forall a. String -> String -> a -> a) ->+  STMDiagnosticStore ->+  NormalizedUri ->+  TextDocumentVersion ->+  DiagnosticsBySource ->+  STM [LSP.Diagnostic]+updateSTMDiagnostics addTag store uri mv newDiagsBySource =     getDiagnosticsFromStore . fromJust <$> STM.focus (Focus.alter update *> Focus.lookup) uri store   where     update (Just(StoreItem mvs dbs))+      | addTag "previous version" (show mvs) $+        addTag "previous count" (show $ Prelude.length $ filter (not.null) $ Map.elems dbs) False = undefined       | mvs == mv = Just (StoreItem mv (newDiagsBySource <> dbs))     update _ = Just (StoreItem mv newDiagsBySource)  -- | Sets the diagnostics for a file and compilation step --   if you want to clear the diagnostics call this with an empty list setStageDiagnostics-    :: NormalizedUri+    :: (forall a. String -> String -> a -> a)+    -> NormalizedUri     -> TextDocumentVersion -- ^ the time that the file these diagnostics originate from was last edited     -> T.Text     -> [LSP.Diagnostic]     -> STMDiagnosticStore     -> STM [LSP.Diagnostic]-setStageDiagnostics uri ver stage diags ds = updateSTMDiagnostics ds uri ver updatedDiags+setStageDiagnostics addTag uri ver stage diags ds = updateSTMDiagnostics addTag ds uri ver updatedDiags   where-    updatedDiags = Map.singleton (Just stage) (SL.toSortedList diags)+    !updatedDiags = Map.singleton (Just stage) $! SL.toSortedList diags  getAllDiagnostics ::     STMDiagnosticStore ->@@ -1238,7 +1291,10 @@                 -- Very important to use mapAccum here so that the tails of                 -- each mapping can be shared, otherwise quadratic space is                 -- used which is evident in long running sessions.-                Map.mapAccumRWithKey (\acc _k (delta, _) -> let new = addDelta delta acc in (new, (delta, acc)))+                EM.mapAccumRWithKey (\acc _k (delta, _) -> let new = addDelta delta acc in (new, (delta, acc)))                   zeroMapping-                  (Map.insert _version (shared_change, zeroMapping) mappingForUri)+                  (EM.insert actual_version (shared_change, zeroMapping) mappingForUri)         shared_change = mkDelta changes+        actual_version = case _version of+          Nothing -> error "Nothing version from server" -- This is a violation of the spec+          Just v  -> v
src/Development/IDE/Core/UseStale.hs view
@@ -1,162 +1,162 @@-{-# LANGUAGE DerivingVia    #-}
-{-# LANGUAGE GADTs          #-}
-{-# LANGUAGE KindSignatures #-}
-{-# LANGUAGE RankNTypes     #-}
-
-module Development.IDE.Core.UseStale
-  ( Age(..)
-  , Tracked
-  , unTrack
-  , PositionMap
-  , TrackedStale (..)
-  , untrackedStaleValue
-  , unsafeMkStale
-  , unsafeMkCurrent
-  , unsafeCopyAge
-  , MapAge (..)
-  , dualPositionMap
-  , useWithStale
-  , useWithStale_
-  ) where
-
-import           Control.Arrow
-import           Control.Category                     (Category)
-import qualified Control.Category                     as C
-import           Control.DeepSeq                      (NFData)
-import           Data.Aeson
-import           Data.Coerce                          (coerce)
-import           Data.Functor                         ((<&>))
-import           Data.Functor.Identity                (Identity (Identity))
-import           Data.Kind                            (Type)
-import           Data.String                          (fromString)
-import           Development.IDE                      (Action, IdeRule,
-                                                       NormalizedFilePath,
-                                                       Range,
-                                                       rangeToRealSrcSpan,
-                                                       realSrcSpanToRange)
-import qualified Development.IDE.Core.PositionMapping as P
-import qualified Development.IDE.Core.Shake           as IDE
-import           Development.IDE.GHC.Compat           (RealSrcSpan, srcSpanFile)
-import           Development.IDE.GHC.Compat.Util      (unpackFS)
-
-
-------------------------------------------------------------------------------
--- | A data kind for 'Tracked'.
-data Age = Current | Stale Type
-
-
-------------------------------------------------------------------------------
--- | Some value, tagged with its age. All 'Current' ages are considered to be
--- the same thing, but 'Stale' values are protected by an untouchable variable
--- to ensure they can't be unified.
-newtype Tracked (age :: Age) a  = UnsafeTracked
-  { unTrack :: a
-  }
-  deriving stock (Functor, Foldable, Traversable)
-  deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData)
-  deriving (Applicative, Monad) via Identity
-
-
-------------------------------------------------------------------------------
--- | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values
--- change. Use the 'Category' instance to compose 'PositionMapping's in order
--- to transform between values of different stale ages.
-newtype PositionMap (from :: Age) (to :: Age) = PositionMap
-  { _getPositionMapping :: P.PositionMapping
-  }
-
-instance Category PositionMap where
-  id  = coerce P.zeroMapping
-  (.) = coerce P.composeDelta
-
-
-------------------------------------------------------------------------------
--- | Get a 'PositionMap' that runs in the opposite direction.
-dualPositionMap :: PositionMap from to -> PositionMap to from
-dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) =
-  PositionMap $ P.PositionMapping $ P.PositionDelta to from
-
-
-------------------------------------------------------------------------------
--- | A pair containing a @'Tracked' 'Stale'@ value, as well as
--- a 'PositionMapping' that will fast-forward values to the current age.
-data TrackedStale a where
-  TrackedStale
-      :: Tracked (Stale s) a
-      -> PositionMap (Stale s) Current
-      -> TrackedStale a
-
-instance Functor TrackedStale where
-  fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm
-
-
-untrackedStaleValue :: TrackedStale a -> a
-untrackedStaleValue (TrackedStale ta _) = coerce ta
-
-
-------------------------------------------------------------------------------
--- | A class for which 'Tracked' values can be run across a 'PositionMapping'
--- to change their ages.
-class MapAge a where
-  {-# MINIMAL mapAgeFrom | mapAgeTo #-}
-  mapAgeFrom :: PositionMap from to -> Tracked to   a -> Maybe (Tracked from a)
-  mapAgeFrom = mapAgeTo . dualPositionMap
-
-  mapAgeTo   :: PositionMap from to -> Tracked from a -> Maybe (Tracked to   a)
-  mapAgeTo = mapAgeFrom . dualPositionMap
-
-
-instance MapAge Range where
-  mapAgeFrom = coerce P.fromCurrentRange
-  mapAgeTo   = coerce P.toCurrentRange
-
-
-instance MapAge RealSrcSpan where
-  mapAgeFrom =
-    invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs))
-              (srcSpanFile &&& realSrcSpanToRange)
-      .  mapAgeFrom
-
-
-------------------------------------------------------------------------------
--- | Helper function for deriving 'MapAge' for values in terms of other
--- instances.
-invMapAge
-    :: (c -> a -> b)
-    -> (b -> (c, a))
-    -> (Tracked from a -> Maybe (Tracked to a))
-    -> Tracked from b
-    -> Maybe (Tracked to b)
-invMapAge to from f t =
-  let (c, t') = unTrack $ fmap from t
-   in fmap (fmap $ to c) $ f $ UnsafeTracked t'
-
-
-unsafeMkCurrent :: age ->  Tracked 'Current age
-unsafeMkCurrent = coerce
-
-
-unsafeMkStale :: age -> Tracked (Stale s) age
-unsafeMkStale = coerce
-
-
-unsafeCopyAge :: Tracked age a -> b -> Tracked age b
-unsafeCopyAge _ = coerce
-
-
--- | Request a Rule result, it not available return the last computed result, if any, which may be stale
-useWithStale :: IdeRule k v
-    => k -> NormalizedFilePath -> Action (Maybe (TrackedStale v))
-useWithStale key file = do
-  x <- IDE.useWithStale key file
-  pure $ x <&> \(v, pm) ->
-    TrackedStale (coerce v) (coerce pm)
-
--- | Request a Rule result, it not available return the last computed result which may be stale.
---   Errors out if none available.
-useWithStale_ :: IdeRule k v
-    => k -> NormalizedFilePath -> Action (TrackedStale v)
-useWithStale_ key file = do
-  (v, pm) <- IDE.useWithStale_ key file
-  pure $ TrackedStale (coerce v) (coerce pm)
-
+{-# LANGUAGE DerivingVia    #-}+{-# LANGUAGE GADTs          #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE RankNTypes     #-}++module Development.IDE.Core.UseStale+  ( Age(..)+  , Tracked+  , unTrack+  , PositionMap+  , TrackedStale (..)+  , untrackedStaleValue+  , unsafeMkStale+  , unsafeMkCurrent+  , unsafeCopyAge+  , MapAge (..)+  , dualPositionMap+  , useWithStale+  , useWithStale_+  ) where++import           Control.Arrow+import           Control.Category                     (Category)+import qualified Control.Category                     as C+import           Control.DeepSeq                      (NFData)+import           Data.Aeson+import           Data.Coerce                          (coerce)+import           Data.Functor                         ((<&>))+import           Data.Functor.Identity                (Identity (Identity))+import           Data.Kind                            (Type)+import           Data.String                          (fromString)+import           Development.IDE                      (Action, IdeRule,+                                                       NormalizedFilePath,+                                                       Range,+                                                       rangeToRealSrcSpan,+                                                       realSrcSpanToRange)+import qualified Development.IDE.Core.PositionMapping as P+import qualified Development.IDE.Core.Shake           as IDE+import           Development.IDE.GHC.Compat           (RealSrcSpan, srcSpanFile)+import           Development.IDE.GHC.Compat.Util      (unpackFS)+++------------------------------------------------------------------------------+-- | A data kind for 'Tracked'.+data Age = Current | Stale Type+++------------------------------------------------------------------------------+-- | Some value, tagged with its age. All 'Current' ages are considered to be+-- the same thing, but 'Stale' values are protected by an untouchable variable+-- to ensure they can't be unified.+newtype Tracked (age :: Age) a  = UnsafeTracked+  { unTrack :: a+  }+  deriving stock (Functor, Foldable, Traversable)+  deriving newtype (Eq, Ord, Show, Read, ToJSON, FromJSON, NFData)+  deriving (Applicative, Monad) via Identity+++------------------------------------------------------------------------------+-- | Like 'P.PositionMapping', but with annotated ages for how 'Tracked' values+-- change. Use the 'Category' instance to compose 'PositionMapping's in order+-- to transform between values of different stale ages.+newtype PositionMap (from :: Age) (to :: Age) = PositionMap+  { _getPositionMapping :: P.PositionMapping+  }++instance Category PositionMap where+  id  = coerce P.zeroMapping+  (.) = coerce P.composeDelta+++------------------------------------------------------------------------------+-- | Get a 'PositionMap' that runs in the opposite direction.+dualPositionMap :: PositionMap from to -> PositionMap to from+dualPositionMap (PositionMap (P.PositionMapping (P.PositionDelta from to))) =+  PositionMap $ P.PositionMapping $ P.PositionDelta to from+++------------------------------------------------------------------------------+-- | A pair containing a @'Tracked' 'Stale'@ value, as well as+-- a 'PositionMapping' that will fast-forward values to the current age.+data TrackedStale a where+  TrackedStale+      :: Tracked (Stale s) a+      -> PositionMap (Stale s) Current+      -> TrackedStale a++instance Functor TrackedStale where+  fmap f (TrackedStale t pm) = TrackedStale (fmap f t) pm+++untrackedStaleValue :: TrackedStale a -> a+untrackedStaleValue (TrackedStale ta _) = coerce ta+++------------------------------------------------------------------------------+-- | A class for which 'Tracked' values can be run across a 'PositionMapping'+-- to change their ages.+class MapAge a where+  {-# MINIMAL mapAgeFrom | mapAgeTo #-}+  mapAgeFrom :: PositionMap from to -> Tracked to   a -> Maybe (Tracked from a)+  mapAgeFrom = mapAgeTo . dualPositionMap++  mapAgeTo   :: PositionMap from to -> Tracked from a -> Maybe (Tracked to   a)+  mapAgeTo = mapAgeFrom . dualPositionMap+++instance MapAge Range where+  mapAgeFrom = coerce P.fromCurrentRange+  mapAgeTo   = coerce P.toCurrentRange+++instance MapAge RealSrcSpan where+  mapAgeFrom =+    invMapAge (\fs -> rangeToRealSrcSpan (fromString $ unpackFS fs))+              (srcSpanFile &&& realSrcSpanToRange)+      .  mapAgeFrom+++------------------------------------------------------------------------------+-- | Helper function for deriving 'MapAge' for values in terms of other+-- instances.+invMapAge+    :: (c -> a -> b)+    -> (b -> (c, a))+    -> (Tracked from a -> Maybe (Tracked to a))+    -> Tracked from b+    -> Maybe (Tracked to b)+invMapAge to from f t =+  let (c, t') = unTrack $ fmap from t+   in fmap (fmap $ to c) $ f $ UnsafeTracked t'+++unsafeMkCurrent :: age ->  Tracked 'Current age+unsafeMkCurrent = coerce+++unsafeMkStale :: age -> Tracked (Stale s) age+unsafeMkStale = coerce+++unsafeCopyAge :: Tracked age a -> b -> Tracked age b+unsafeCopyAge _ = coerce+++-- | Request a Rule result, it not available return the last computed result, if any, which may be stale+useWithStale :: IdeRule k v+    => k -> NormalizedFilePath -> Action (Maybe (TrackedStale v))+useWithStale key file = do+  x <- IDE.useWithStale key file+  pure $ x <&> \(v, pm) ->+    TrackedStale (coerce v) (coerce pm)++-- | Request a Rule result, it not available return the last computed result which may be stale.+--   Errors out if none available.+useWithStale_ :: IdeRule k v+    => k -> NormalizedFilePath -> Action (TrackedStale v)+useWithStale_ key file = do+  (v, pm) <- IDE.useWithStale_ key file+  pure $ TrackedStale (coerce v) (coerce pm)+
src/Development/IDE/GHC/CPP.hs view
@@ -1,63 +1,63 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.
--- SPDX-License-Identifier: Apache-2.0
-
-{-# LANGUAGE CPP                      #-}
-{-# LANGUAGE NondecreasingIndentation #-}
-
------------------------------------------------------------------------------
---
--- GHC Driver
---
--- (c) The University of Glasgow 2005
---
------------------------------------------------------------------------------
-
-module Development.IDE.GHC.CPP(doCpp, addOptP)
-where
-
-import           Development.IDE.GHC.Compat      as Compat
-import           GHC
-#if !MIN_VERSION_ghc(8,10,0)
-import qualified Development.IDE.GHC.Compat.CPP  as CPP
-#else
-import           Development.IDE.GHC.Compat.Util
-#endif
-
-#if MIN_VERSION_ghc(9,0,0)
-import qualified GHC.Driver.Pipeline             as Pipeline
-import           GHC.Settings
-#else
-#if MIN_VERSION_ghc (8,10,0)
-import qualified DriverPipeline                  as Pipeline
-import           ToolSettings
-#else
-import           DynFlags
-#endif
-#endif
-
-addOptP :: String -> DynFlags -> DynFlags
-#if MIN_VERSION_ghc (8,10,0)
-addOptP f = alterToolSettings $ \s -> s
-          { toolSettings_opt_P             = f : toolSettings_opt_P s
-          , toolSettings_opt_P_fingerprint = fingerprintStrings (f : toolSettings_opt_P s)
-          }
-  where
-    fingerprintStrings ss = fingerprintFingerprints $ map fingerprintString ss
-    alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) }
-#else
-addOptP opt = onSettings (onOptP (opt:))
-  where
-    onSettings f x = x{settings = f $ settings x}
-    onOptP f x = x{sOpt_P = f $ sOpt_P x}
-#endif
-
-doCpp :: HscEnv -> Bool -> FilePath -> FilePath -> IO ()
-doCpp env raw input_fn output_fn =
-#if MIN_VERSION_ghc (9,2,0)
-    Pipeline.doCpp (hsc_logger env) (hsc_tmpfs env) (hsc_dflags env) (hsc_unit_env env) raw input_fn output_fn
-#elif MIN_VERSION_ghc (8,10,0)
-    Pipeline.doCpp (hsc_dflags env) raw input_fn output_fn
-#else
-    CPP.doCpp (hsc_dflags env) raw input_fn output_fn
-#endif
-
+-- Copyright (c) 2019 The DAML Authors. All rights reserved.+-- SPDX-License-Identifier: Apache-2.0++{-# LANGUAGE CPP                      #-}+{-# LANGUAGE NondecreasingIndentation #-}++-----------------------------------------------------------------------------+--+-- GHC Driver+--+-- (c) The University of Glasgow 2005+--+-----------------------------------------------------------------------------++module Development.IDE.GHC.CPP(doCpp, addOptP)+where++import           Development.IDE.GHC.Compat      as Compat+import           GHC+#if !MIN_VERSION_ghc(8,10,0)+import qualified Development.IDE.GHC.Compat.CPP  as CPP+#else+import           Development.IDE.GHC.Compat.Util+#endif++#if MIN_VERSION_ghc(9,0,0)+import qualified GHC.Driver.Pipeline             as Pipeline+import           GHC.Settings+#else+#if MIN_VERSION_ghc (8,10,0)+import qualified DriverPipeline                  as Pipeline+import           ToolSettings+#else+import           DynFlags+#endif+#endif++addOptP :: String -> DynFlags -> DynFlags+#if MIN_VERSION_ghc (8,10,0)+addOptP f = alterToolSettings $ \s -> s+          { toolSettings_opt_P             = f : toolSettings_opt_P s+          , toolSettings_opt_P_fingerprint = fingerprintStrings (f : toolSettings_opt_P s)+          }+  where+    fingerprintStrings ss = fingerprintFingerprints $ map fingerprintString ss+    alterToolSettings f dynFlags = dynFlags { toolSettings = f (toolSettings dynFlags) }+#else+addOptP opt = onSettings (onOptP (opt:))+  where+    onSettings f x = x{settings = f $ settings x}+    onOptP f x = x{sOpt_P = f $ sOpt_P x}+#endif++doCpp :: HscEnv -> Bool -> FilePath -> FilePath -> IO ()+doCpp env raw input_fn output_fn =+#if MIN_VERSION_ghc (9,2,0)+    Pipeline.doCpp (hsc_logger env) (hsc_tmpfs env) (hsc_dflags env) (hsc_unit_env env) raw input_fn output_fn+#elif MIN_VERSION_ghc (8,10,0)+    Pipeline.doCpp (hsc_dflags env) raw input_fn output_fn+#else+    CPP.doCpp (hsc_dflags env) raw input_fn output_fn+#endif+
src/Development/IDE/GHC/Compat.hs view
@@ -20,6 +20,7 @@     reLocA,     getMessages',     pattern PFailedWithErrorMessages,+    isObjectLinkable,  #if !MIN_VERSION_ghc(9,0,1)     RefMap,@@ -28,6 +29,7 @@ #if MIN_VERSION_ghc(9,2,0)     extendModSummaryNoDeps,     emsModSummary,+    myCoreToStgExpr, #endif      nodeInfo',@@ -69,6 +71,39 @@     Option (..),     runUnlit,     runPp,++    -- * Recompilation avoidance+    hscCompileCoreExprHook,+    CoreExpr,+    simplifyExpr,+    tidyExpr,+    emptyTidyEnv,+    corePrepExpr,+    lintInteractiveExpr,+    icInteractiveModule,+    HomePackageTable,+    lookupHpt,+    Dependencies(dep_mods),+    bcoFreeNames,+    ModIfaceAnnotation,+    pattern Annotation,+    AnnTarget(ModuleTarget),+    extendAnnEnvList,+    module UniqDSet,+    module UniqSet,+    module UniqDFM,+    getDependentMods,+#if MIN_VERSION_ghc(9,2,0)+    loadExpr,+    byteCodeGen,+    bc_bcos,+    loadDecls,+    hscInterp,+    expectJust,+#else+    coreExprToBCOs,+    linkExpr,+#endif     ) where  import           Development.IDE.GHC.Compat.Core@@ -84,7 +119,48 @@ import           GHC                                   hiding (HasSrcSpan,                                                         ModLocation,                                                         RealSrcSpan, getLoc,-                                                        lookupName)+                                                        lookupName, exprType)+#if MIN_VERSION_ghc(9,0,0)+import GHC.Driver.Hooks (hscCompileCoreExprHook)+import GHC.Core (CoreExpr, CoreProgram)+import qualified GHC.Core.Opt.Pipeline as GHC+import GHC.Core.Tidy (tidyExpr)+import GHC.Types.Var.Env (emptyTidyEnv)+import qualified GHC.CoreToStg.Prep as GHC+import GHC.Core.Lint (lintInteractiveExpr)+#if MIN_VERSION_ghc(9,2,0)+import GHC.Unit.Home.ModInfo (lookupHpt, HomePackageTable)+import GHC.Runtime.Context (icInteractiveModule)+import GHC.Unit.Module.Deps (Dependencies(dep_mods))+import GHC.Linker.Types (isObjectLinkable)+import GHC.Linker.Loader (loadExpr)+#else+import GHC.CoreToByteCode (coreExprToBCOs)+import GHC.Driver.Types (Dependencies(dep_mods), icInteractiveModule, lookupHpt, HomePackageTable)+import GHC.Runtime.Linker (linkExpr)+#endif+import GHC.ByteCode.Asm (bcoFreeNames)+import GHC.Types.Annotations (Annotation(..), AnnTarget(ModuleTarget), extendAnnEnvList)+import GHC.Types.Unique.DSet as UniqDSet+import GHC.Types.Unique.Set as UniqSet+import GHC.Types.Unique.DFM  as UniqDFM+#else+import Hooks (hscCompileCoreExprHook)+import CoreSyn (CoreExpr)+import qualified SimplCore as GHC+import CoreTidy (tidyExpr)+import VarEnv (emptyTidyEnv)+import CorePrep (corePrepExpr)+import CoreLint (lintInteractiveExpr)+import ByteCodeGen (coreExprToBCOs)+import HscTypes (icInteractiveModule, HomePackageTable, lookupHpt, Dependencies(dep_mods))+import Linker (linkExpr)+import ByteCodeAsm (bcoFreeNames)+import Annotations (Annotation(..), AnnTarget(ModuleTarget), extendAnnEnvList)+import UniqDSet+import UniqSet+import UniqDFM+#endif  #if MIN_VERSION_ghc(9,0,0) import           GHC.Data.StringBuffer@@ -142,12 +218,90 @@ import           Bag                                   (unitBag) #endif +#if MIN_VERSION_ghc(9,2,0)+import GHC.Types.CostCentre+import GHC.Stg.Syntax+import GHC.Types.IPE+import GHC.Stg.Syntax+import GHC.Types.IPE+import GHC.Types.CostCentre+import GHC.Core+import GHC.Builtin.Uniques+import GHC.Runtime.Interpreter+import GHC.StgToByteCode+import GHC.Stg.Pipeline+import GHC.ByteCode.Types+import GHC.Linker.Loader (loadDecls)+import GHC.Data.Maybe+import GHC.CoreToStg+#endif++type ModIfaceAnnotation = Annotation++#if MIN_VERSION_ghc(9,2,0)+myCoreToStgExpr :: Logger -> DynFlags -> InteractiveContext+                -> Module -> ModLocation -> CoreExpr+                -> IO ( Id+                      , [StgTopBinding]+                      , InfoTableProvMap+                      , CollectedCCs )+myCoreToStgExpr logger dflags ictxt this_mod ml prepd_expr = do+    {- Create a temporary binding (just because myCoreToStg needs a+       binding for the stg2stg step) -}+    let bco_tmp_id = mkSysLocal (fsLit "BCO_toplevel")+                                (mkPseudoUniqueE 0)+                                Many+                                (exprType prepd_expr)+    (stg_binds, prov_map, collected_ccs) <-+       myCoreToStg logger+                   dflags+                   ictxt+                   this_mod+                   ml+                   [NonRec bco_tmp_id prepd_expr]+    return (bco_tmp_id, stg_binds, prov_map, collected_ccs)++myCoreToStg :: Logger -> DynFlags -> InteractiveContext+            -> Module -> ModLocation -> CoreProgram+            -> IO ( [StgTopBinding] -- output program+                  , InfoTableProvMap+                  , CollectedCCs )  -- CAF cost centre info (declared and used)+myCoreToStg logger dflags ictxt this_mod ml prepd_binds = do+    let (stg_binds, denv, cost_centre_info)+         = {-# SCC "Core2Stg" #-}+           coreToStg dflags this_mod ml prepd_binds++    stg_binds2+        <- {-# SCC "Stg2Stg" #-}+           stg2stg logger dflags ictxt this_mod stg_binds++    return (stg_binds2, denv, cost_centre_info)+#endif++ #if !MIN_VERSION_ghc(9,2,0) reLoc :: Located a -> Located a reLoc = id  reLocA :: Located a -> Located a reLocA = id+#endif++getDependentMods :: ModIface -> [ModuleName]+#if MIN_VERSION_ghc(9,0,0)+getDependentMods = map gwib_mod . dep_mods . mi_deps+#else+getDependentMods = map fst . dep_mods . mi_deps+#endif++simplifyExpr :: DynFlags -> HscEnv -> CoreExpr -> IO CoreExpr+#if MIN_VERSION_ghc(9,0,0)+simplifyExpr _ = GHC.simplifyExpr++corePrepExpr :: DynFlags -> HscEnv -> CoreExpr -> IO CoreExpr+corePrepExpr _ = GHC.corePrepExpr+#else+simplifyExpr df _ = GHC.simplifyExpr df #endif  #if !MIN_VERSION_ghc(8,8,0)
src/Development/IDE/GHC/Compat/CPP.hs view
@@ -1,204 +1,204 @@--- Copyright (c) 2019 The DAML Authors. All rights reserved.
--- SPDX-License-Identifier: Apache-2.0
-
--- Copied from https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs on 14 May 2019
--- Requested to be exposed at https://gitlab.haskell.org/ghc/ghc/merge_requests/944.
--- Update the above MR got merged to master on 31 May 2019. When it becomes avialable to ghc-lib, this file can be removed.
-
-{- HLINT ignore -} -- since copied from upstream
-
-{-# LANGUAGE CPP #-}
-{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
--- | Re-export 'doCpp' for GHC < 8.10.
---
--- Later versions export what we need.
-module Development.IDE.GHC.Compat.CPP (
-    doCpp
-    ) where
-
-import           FileCleanup
-import           Packages
-import           Panic
-import           SysTools
-#if MIN_VERSION_ghc(8,8,2)
-import           LlvmCodeGen                (llvmVersionList)
-#elif MIN_VERSION_ghc(8,8,0)
-import           LlvmCodeGen                (LlvmVersion (..))
-#endif
-import           Control.Monad
-import           Data.List                  (intercalate)
-import           Data.Maybe
-import           Data.Version
-import           DynFlags
-import           Module                     (rtsUnitId, toInstalledUnitId)
-import           System.Directory
-import           System.FilePath
-import           System.Info
-
-import           Development.IDE.GHC.Compat as Compat
-
-doCpp :: DynFlags -> Bool -> FilePath -> FilePath -> IO ()
-doCpp dflags raw input_fn output_fn = do
-    let hscpp_opts = picPOpts dflags
-    let cmdline_include_paths = includePaths dflags
-
-    pkg_include_dirs <- getPackageIncludePath dflags []
-    let include_paths_global = foldr (\ x xs -> ("-I" ++ x) : xs) []
-          (includePathsGlobal cmdline_include_paths ++ pkg_include_dirs)
-    let include_paths_quote = foldr (\ x xs -> ("-iquote" ++ x) : xs) []
-          (includePathsQuote cmdline_include_paths)
-    let include_paths = include_paths_quote ++ include_paths_global
-
-    let verbFlags = getVerbFlags dflags
-
-    let cpp_prog args | raw       = SysTools.runCpp dflags args
-#if MIN_VERSION_ghc(8,10,0)
-                      | otherwise = SysTools.runCc Nothing
-#else
-                      | otherwise = SysTools.runCc
-#endif
-                                          dflags (SysTools.Option "-E" : args)
-
-    let target_defs =
-          -- NEIL: Patched to use System.Info instead of constants from CPP
-          [ "-D" ++ os     ++ "_BUILD_OS",
-            "-D" ++ arch   ++ "_BUILD_ARCH",
-            "-D" ++ os     ++ "_HOST_OS",
-            "-D" ++ arch   ++ "_HOST_ARCH" ]
-        -- remember, in code we *compile*, the HOST is the same our TARGET,
-        -- and BUILD is the same as our HOST.
-
-    let sse_defs =
-          [ "-D__SSE__"      | isSseEnabled      dflags ] ++
-          [ "-D__SSE2__"     | isSse2Enabled     dflags ] ++
-          [ "-D__SSE4_2__"   | isSse4_2Enabled   dflags ]
-
-    let avx_defs =
-          [ "-D__AVX__"      | isAvxEnabled      dflags ] ++
-          [ "-D__AVX2__"     | isAvx2Enabled     dflags ] ++
-          [ "-D__AVX512CD__" | isAvx512cdEnabled dflags ] ++
-          [ "-D__AVX512ER__" | isAvx512erEnabled dflags ] ++
-          [ "-D__AVX512F__"  | isAvx512fEnabled  dflags ] ++
-          [ "-D__AVX512PF__" | isAvx512pfEnabled dflags ]
-
-    backend_defs <- getBackendDefs dflags
-
-    let th_defs = [ "-D__GLASGOW_HASKELL_TH__" ]
-    -- Default CPP defines in Haskell source
-    ghcVersionH <- getGhcVersionPathName dflags
-    let hsSourceCppOpts = [ "-include", ghcVersionH ]
-
-    -- MIN_VERSION macros
-    let uids = explicitPackages (pkgState dflags)
-        pkgs = catMaybes (map (lookupPackage dflags) uids)
-    mb_macro_include <-
-        if not (null pkgs) && gopt Opt_VersionMacros dflags
-            then do macro_stub <- newTempName dflags TFL_CurrentModule "h"
-                    writeFile macro_stub (generatePackageVersionMacros pkgs)
-                    -- Include version macros for every *exposed* package.
-                    -- Without -hide-all-packages and with a package database
-                    -- size of 1000 packages, it takes cpp an estimated 2
-                    -- milliseconds to process this file. See #10970
-                    -- comment 8.
-                    return [SysTools.FileOption "-include" macro_stub]
-            else return []
-
-    cpp_prog       (   map SysTools.Option verbFlags
-                    ++ map SysTools.Option include_paths
-                    ++ map SysTools.Option hsSourceCppOpts
-                    ++ map SysTools.Option target_defs
-                    ++ map SysTools.Option backend_defs
-                    ++ map SysTools.Option th_defs
-                    ++ map SysTools.Option hscpp_opts
-                    ++ map SysTools.Option sse_defs
-                    ++ map SysTools.Option avx_defs
-                    ++ mb_macro_include
-        -- Set the language mode to assembler-with-cpp when preprocessing. This
-        -- alleviates some of the C99 macro rules relating to whitespace and the hash
-        -- operator, which we tend to abuse. Clang in particular is not very happy
-        -- about this.
-                    ++ [ SysTools.Option     "-x"
-                       , SysTools.Option     "assembler-with-cpp"
-                       , SysTools.Option     input_fn
-        -- We hackily use Option instead of FileOption here, so that the file
-        -- name is not back-slashed on Windows.  cpp is capable of
-        -- dealing with / in filenames, so it works fine.  Furthermore
-        -- if we put in backslashes, cpp outputs #line directives
-        -- with *double* backslashes.   And that in turn means that
-        -- our error messages get double backslashes in them.
-        -- In due course we should arrange that the lexer deals
-        -- with these \\ escapes properly.
-                       , SysTools.Option     "-o"
-                       , SysTools.FileOption "" output_fn
-                       ])
-
-getBackendDefs :: DynFlags -> IO [String]
-getBackendDefs dflags | hscTarget dflags == HscLlvm = do
-    llvmVer <- figureLlvmVersion dflags
-    return $ case llvmVer of
-#if MIN_VERSION_ghc(8,8,2)
-               Just v
-                 | [m] <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, 0) ]
-                 | m:n:_   <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, n) ]
-#elif MIN_VERSION_ghc(8,8,0)
-               Just (LlvmVersion n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (n,0) ]
-               Just (LlvmVersionOld m n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m,n) ]
-#else
-               Just n -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format n ]
-#endif
-               _      -> []
-  where
-    format (major, minor)
-      | minor >= 100 = error "getBackendDefs: Unsupported minor version"
-      | otherwise = show $ (100 * major + minor :: Int) -- Contract is Int
-
-getBackendDefs _ =
-    return []
-
--- ---------------------------------------------------------------------------
--- Macros (cribbed from Cabal)
-
-generatePackageVersionMacros :: [Compat.UnitInfo] -> String
-generatePackageVersionMacros pkgs = concat
-  -- Do not add any C-style comments. See #3389.
-  [ generateMacros "" pkgname version
-  | pkg <- pkgs
-  , let version = packageVersion pkg
-        pkgname = map fixchar (packageNameString pkg)
-  ]
-
-fixchar :: Char -> Char
-fixchar '-' = '_'
-fixchar c   = c
-
-generateMacros :: String -> String -> Version -> String
-generateMacros prefix name version =
-  concat
-  ["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n"
-  ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"
-  ,"  (major1) <  ",major1," || \\\n"
-  ,"  (major1) == ",major1," && (major2) <  ",major2," || \\\n"
-  ,"  (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"
-  ,"\n\n"
-  ]
-  where
-    (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)
-
-
--- | Find out path to @ghcversion.h@ file
-getGhcVersionPathName :: DynFlags -> IO FilePath
-getGhcVersionPathName dflags = do
-  candidates <- case ghcVersionFile dflags of
-    Just path -> return [path]
-    Nothing -> (map (</> "ghcversion.h")) <$>
-               (getPackageIncludePath dflags [toInstalledUnitId rtsUnit])
-
-  found <- filterM doesFileExist candidates
-  case found of
-      []    -> throwGhcExceptionIO (InstallationError
-                                    ("ghcversion.h missing; tried: "
-                                      ++ intercalate ", " candidates))
-      (x:_) -> return x
-
-rtsUnit :: UnitId
-rtsUnit = Module.rtsUnitId
+-- Copyright (c) 2019 The DAML Authors. All rights reserved.+-- SPDX-License-Identifier: Apache-2.0++-- Copied from https://github.com/ghc/ghc/blob/master/compiler/main/DriverPipeline.hs on 14 May 2019+-- Requested to be exposed at https://gitlab.haskell.org/ghc/ghc/merge_requests/944.+-- Update the above MR got merged to master on 31 May 2019. When it becomes avialable to ghc-lib, this file can be removed.++{- HLINT ignore -} -- since copied from upstream++{-# LANGUAGE CPP #-}+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}+-- | Re-export 'doCpp' for GHC < 8.10.+--+-- Later versions export what we need.+module Development.IDE.GHC.Compat.CPP (+    doCpp+    ) where++import           FileCleanup+import           Packages+import           Panic+import           SysTools+#if MIN_VERSION_ghc(8,8,2)+import           LlvmCodeGen                (llvmVersionList)+#elif MIN_VERSION_ghc(8,8,0)+import           LlvmCodeGen                (LlvmVersion (..))+#endif+import           Control.Monad+import           Data.List                  (intercalate)+import           Data.Maybe+import           Data.Version+import           DynFlags+import           Module                     (rtsUnitId, toInstalledUnitId)+import           System.Directory+import           System.FilePath+import           System.Info++import           Development.IDE.GHC.Compat as Compat++doCpp :: DynFlags -> Bool -> FilePath -> FilePath -> IO ()+doCpp dflags raw input_fn output_fn = do+    let hscpp_opts = picPOpts dflags+    let cmdline_include_paths = includePaths dflags++    pkg_include_dirs <- getPackageIncludePath dflags []+    let include_paths_global = foldr (\ x xs -> ("-I" ++ x) : xs) []+          (includePathsGlobal cmdline_include_paths ++ pkg_include_dirs)+    let include_paths_quote = foldr (\ x xs -> ("-iquote" ++ x) : xs) []+          (includePathsQuote cmdline_include_paths)+    let include_paths = include_paths_quote ++ include_paths_global++    let verbFlags = getVerbFlags dflags++    let cpp_prog args | raw       = SysTools.runCpp dflags args+#if MIN_VERSION_ghc(8,10,0)+                      | otherwise = SysTools.runCc Nothing+#else+                      | otherwise = SysTools.runCc+#endif+                                          dflags (SysTools.Option "-E" : args)++    let target_defs =+          -- NEIL: Patched to use System.Info instead of constants from CPP+          [ "-D" ++ os     ++ "_BUILD_OS",+            "-D" ++ arch   ++ "_BUILD_ARCH",+            "-D" ++ os     ++ "_HOST_OS",+            "-D" ++ arch   ++ "_HOST_ARCH" ]+        -- remember, in code we *compile*, the HOST is the same our TARGET,+        -- and BUILD is the same as our HOST.++    let sse_defs =+          [ "-D__SSE__"      | isSseEnabled      dflags ] +++          [ "-D__SSE2__"     | isSse2Enabled     dflags ] +++          [ "-D__SSE4_2__"   | isSse4_2Enabled   dflags ]++    let avx_defs =+          [ "-D__AVX__"      | isAvxEnabled      dflags ] +++          [ "-D__AVX2__"     | isAvx2Enabled     dflags ] +++          [ "-D__AVX512CD__" | isAvx512cdEnabled dflags ] +++          [ "-D__AVX512ER__" | isAvx512erEnabled dflags ] +++          [ "-D__AVX512F__"  | isAvx512fEnabled  dflags ] +++          [ "-D__AVX512PF__" | isAvx512pfEnabled dflags ]++    backend_defs <- getBackendDefs dflags++    let th_defs = [ "-D__GLASGOW_HASKELL_TH__" ]+    -- Default CPP defines in Haskell source+    ghcVersionH <- getGhcVersionPathName dflags+    let hsSourceCppOpts = [ "-include", ghcVersionH ]++    -- MIN_VERSION macros+    let uids = explicitPackages (pkgState dflags)+        pkgs = catMaybes (map (lookupPackage dflags) uids)+    mb_macro_include <-+        if not (null pkgs) && gopt Opt_VersionMacros dflags+            then do macro_stub <- newTempName dflags TFL_CurrentModule "h"+                    writeFile macro_stub (generatePackageVersionMacros pkgs)+                    -- Include version macros for every *exposed* package.+                    -- Without -hide-all-packages and with a package database+                    -- size of 1000 packages, it takes cpp an estimated 2+                    -- milliseconds to process this file. See #10970+                    -- comment 8.+                    return [SysTools.FileOption "-include" macro_stub]+            else return []++    cpp_prog       (   map SysTools.Option verbFlags+                    ++ map SysTools.Option include_paths+                    ++ map SysTools.Option hsSourceCppOpts+                    ++ map SysTools.Option target_defs+                    ++ map SysTools.Option backend_defs+                    ++ map SysTools.Option th_defs+                    ++ map SysTools.Option hscpp_opts+                    ++ map SysTools.Option sse_defs+                    ++ map SysTools.Option avx_defs+                    ++ mb_macro_include+        -- Set the language mode to assembler-with-cpp when preprocessing. This+        -- alleviates some of the C99 macro rules relating to whitespace and the hash+        -- operator, which we tend to abuse. Clang in particular is not very happy+        -- about this.+                    ++ [ SysTools.Option     "-x"+                       , SysTools.Option     "assembler-with-cpp"+                       , SysTools.Option     input_fn+        -- We hackily use Option instead of FileOption here, so that the file+        -- name is not back-slashed on Windows.  cpp is capable of+        -- dealing with / in filenames, so it works fine.  Furthermore+        -- if we put in backslashes, cpp outputs #line directives+        -- with *double* backslashes.   And that in turn means that+        -- our error messages get double backslashes in them.+        -- In due course we should arrange that the lexer deals+        -- with these \\ escapes properly.+                       , SysTools.Option     "-o"+                       , SysTools.FileOption "" output_fn+                       ])++getBackendDefs :: DynFlags -> IO [String]+getBackendDefs dflags | hscTarget dflags == HscLlvm = do+    llvmVer <- figureLlvmVersion dflags+    return $ case llvmVer of+#if MIN_VERSION_ghc(8,8,2)+               Just v+                 | [m] <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, 0) ]+                 | m:n:_   <- llvmVersionList v -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m, n) ]+#elif MIN_VERSION_ghc(8,8,0)+               Just (LlvmVersion n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (n,0) ]+               Just (LlvmVersionOld m n) -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format (m,n) ]+#else+               Just n -> [ "-D__GLASGOW_HASKELL_LLVM__=" ++ format n ]+#endif+               _      -> []+  where+    format (major, minor)+      | minor >= 100 = error "getBackendDefs: Unsupported minor version"+      | otherwise = show $ (100 * major + minor :: Int) -- Contract is Int++getBackendDefs _ =+    return []++-- ---------------------------------------------------------------------------+-- Macros (cribbed from Cabal)++generatePackageVersionMacros :: [Compat.UnitInfo] -> String+generatePackageVersionMacros pkgs = concat+  -- Do not add any C-style comments. See #3389.+  [ generateMacros "" pkgname version+  | pkg <- pkgs+  , let version = packageVersion pkg+        pkgname = map fixchar (packageNameString pkg)+  ]++fixchar :: Char -> Char+fixchar '-' = '_'+fixchar c   = c++generateMacros :: String -> String -> Version -> String+generateMacros prefix name version =+  concat+  ["#define ", prefix, "VERSION_",name," ",show (showVersion version),"\n"+  ,"#define MIN_", prefix, "VERSION_",name,"(major1,major2,minor) (\\\n"+  ,"  (major1) <  ",major1," || \\\n"+  ,"  (major1) == ",major1," && (major2) <  ",major2," || \\\n"+  ,"  (major1) == ",major1," && (major2) == ",major2," && (minor) <= ",minor,")"+  ,"\n\n"+  ]+  where+    (major1:major2:minor:_) = map show (versionBranch version ++ repeat 0)+++-- | Find out path to @ghcversion.h@ file+getGhcVersionPathName :: DynFlags -> IO FilePath+getGhcVersionPathName dflags = do+  candidates <- case ghcVersionFile dflags of+    Just path -> return [path]+    Nothing -> (map (</> "ghcversion.h")) <$>+               (getPackageIncludePath dflags [toInstalledUnitId rtsUnit])++  found <- filterM doesFileExist candidates+  case found of+      []    -> throwGhcExceptionIO (InstallationError+                                    ("ghcversion.h missing; tried: "+                                      ++ intercalate ", " candidates))+      (x:_) -> return x++rtsUnit :: UnitId+rtsUnit = Module.rtsUnitId
src/Development/IDE/GHC/Compat/Core.hs view
@@ -141,6 +141,9 @@     ImportSpec(..),     -- * SourceText     SourceText(..),+#if !MIN_VERSION_ghc(9,2,0)+    rationalFromFractionalLit,+#endif     -- * Name     tyThingParent_maybe,     -- * Ways@@ -1075,4 +1078,9 @@ #if !MIN_VERSION_ghc(9,2,0) pattern HsLet xlet localBinds expr <- GHC.HsLet xlet (SrcLoc.unLoc -> localBinds) expr pattern LetStmt xlet localBinds <- GHC.LetStmt xlet (SrcLoc.unLoc -> localBinds)+#endif++#if !MIN_VERSION_ghc(9,2,0)+rationalFromFractionalLit :: FractionalLit -> Rational+rationalFromFractionalLit = fl_value #endif
src/Development/IDE/GHC/Compat/Outputable.hs view
@@ -8,11 +8,12 @@     showSDocForUser,     ppr, pprPanic, text, vcat, (<+>), ($$), empty, hang, nest,     printSDocQualifiedUnsafe,-    printNameWithoutUniques,-    printSDocAllTheWay,+    printWithoutUniques,     mkPrintUnqualified,     mkPrintUnqualifiedDefault,     PrintUnqualified(..),+    defaultUserStyle,+    withPprStyle,     -- * Parser errors     PsWarning,     PsError,@@ -43,7 +44,8 @@ import           GHC.Types.SrcLoc import           GHC.Unit.State import           GHC.Utils.Error                 hiding (mkWarnMsg)-import           GHC.Utils.Outputable+import           GHC.Utils.Outputable            as Out hiding (defaultUserStyle)+import qualified GHC.Utils.Outputable            as Out import           GHC.Utils.Panic #elif MIN_VERSION_ghc(9,0,0) import           GHC.Driver.Session@@ -52,25 +54,37 @@ import           GHC.Types.SrcLoc import           GHC.Utils.Error                 as Err hiding (mkWarnMsg) import qualified GHC.Utils.Error                 as Err-import           GHC.Utils.Outputable            as Out+import           GHC.Utils.Outputable            as Out hiding (defaultUserStyle)+import qualified GHC.Utils.Outputable            as Out #else import           Development.IDE.GHC.Compat.Core (GlobalRdrEnv) import           DynFlags import           ErrUtils                        hiding (mkWarnMsg) import qualified ErrUtils                        as Err import           HscTypes-import           Outputable                      as Out+import           Outputable                      as Out hiding (defaultUserStyle)+import qualified Outputable                      as Out import           SrcLoc #endif -printNameWithoutUniques :: Outputable a => a -> String-printNameWithoutUniques =+-- | A compatible function to print `Outputable` instances+-- without unique symbols.+--+-- It print with a user-friendly style like: `a_a4ME` as `a`.+printWithoutUniques :: Outputable a => a -> String+printWithoutUniques = #if MIN_VERSION_ghc(9,2,0)-  renderWithContext (defaultSDocContext { sdocSuppressUniques = True }) . ppr+  renderWithContext (defaultSDocContext+    {+      sdocStyle = defaultUserStyle+    , sdocSuppressUniques = True+    , sdocCanUseUnicode = True+    }) . ppr #else-  printSDocAllTheWay dyn . ppr-  where-    dyn = unsafeGlobalDynFlags `gopt_set` Opt_SuppressUniques+  go . ppr+    where+      go sdoc = oldRenderWithStyle dflags sdoc (oldMkUserStyle dflags neverQualify AllTheWay)+      dflags = unsafeGlobalDynFlags `gopt_set` Opt_SuppressUniques #endif  printSDocQualifiedUnsafe :: PrintUnqualified -> SDoc -> String@@ -86,15 +100,7 @@     showSDocForUser unsafeGlobalDynFlags unqual doc #endif -printSDocAllTheWay :: DynFlags -> SDoc -> String-#if MIN_VERSION_ghc(9,2,0)-printSDocAllTheWay dflags sdoc = renderWithContext ctxt sdoc-  where-    ctxt = initSDocContext dflags (mkUserStyle neverQualify AllTheWay)-#else-printSDocAllTheWay dflags sdoc = oldRenderWithStyle dflags sdoc (oldMkUserStyle dflags Out.neverQualify Out.AllTheWay)--#if  MIN_VERSION_ghc(9,0,0)+#if MIN_VERSION_ghc(9,0,0) && !MIN_VERSION_ghc(9,2,0) oldRenderWithStyle dflags sdoc sty = Out.renderWithStyle (initSDocContext dflags sty) sdoc oldMkUserStyle _ = Out.mkUserStyle oldMkErrStyle _ = Out.mkErrStyle@@ -102,8 +108,7 @@ oldFormatErrDoc :: DynFlags -> Err.ErrDoc -> Out.SDoc oldFormatErrDoc dflags = Err.formatErrDoc dummySDocContext   where dummySDocContext = initSDocContext dflags Out.defaultUserStyle--#else+#elif !MIN_VERSION_ghc(9,0,0) oldRenderWithStyle :: DynFlags -> Out.SDoc -> Out.PprStyle -> String oldRenderWithStyle = Out.renderWithStyle @@ -116,7 +121,6 @@ oldFormatErrDoc :: DynFlags -> Err.ErrDoc -> Out.SDoc oldFormatErrDoc = Err.formatErrDoc #endif-#endif  pprWarning :: PsWarning -> MsgEnvelope DecoratedSDoc pprWarning =@@ -164,7 +168,7 @@ mkPrintUnqualifiedDefault :: HscEnv -> GlobalRdrEnv -> PrintUnqualified mkPrintUnqualifiedDefault env = #if MIN_VERSION_ghc(9,2,0)-  -- GHC 9.2.1 version+  -- GHC 9.2 version   -- mkPrintUnqualified :: UnitEnv -> GlobalRdrEnv -> PrintUnqualified   mkPrintUnqualified (hsc_unit_env env) #else@@ -177,4 +181,11 @@   const Error.mkWarnMsg #else   Err.mkWarnMsg+#endif++defaultUserStyle :: PprStyle+#if MIN_VERSION_ghc(9,0,0)+defaultUserStyle = Out.defaultUserStyle+#else+defaultUserStyle = Out.defaultUserStyle unsafeGlobalDynFlags #endif
src/Development/IDE/GHC/Compat/Parser.hs view
@@ -41,6 +41,8 @@ #if !MIN_VERSION_ghc(9,2,0)     Anno.AnnotationComment(..), #endif+    pattern EpaLineComment,+    pattern EpaBlockComment     ) where  #if MIN_VERSION_ghc(9,0,0)@@ -51,12 +53,18 @@ import qualified GHC.Parser.Lexer                as Lexer import           GHC.Types.SrcLoc                (PsSpan (..)) #if MIN_VERSION_ghc(9,2,0)-import           GHC                             (pm_extra_src_files,+import           GHC                             (Anchor (anchor),+                                                  EpAnnComments (priorComments),+                                                  EpaComment (EpaComment),+                                                  EpaCommentTok (..),+                                                  epAnnComments,+                                                  pm_extra_src_files,                                                   pm_mod_summary,                                                   pm_parsed_source) import qualified GHC import qualified GHC.Driver.Config               as Config-import           GHC.Hs                          (hpm_module, hpm_src_files)+import           GHC.Hs                          (LEpaComment, hpm_module,+                                                  hpm_src_files) import           GHC.Parser.Lexer                hiding (initParserState) #endif #else@@ -100,6 +108,8 @@ #endif  #if MIN_VERSION_ghc(9,2,0)+-- GHC 9.2 does not have ApiAnns anymore packaged in ParsedModule. Now the+-- annotations are found in the ast. type ApiAnns = () #else type ApiAnns = Anno.ApiAnns@@ -154,4 +164,9 @@      Map.fromList ((SrcLoc.noSrcSpan,comment_q pst)                   :annotations_comments pst)) #endif+#endif++#if !MIN_VERSION_ghc(9,2,0)+pattern EpaLineComment a = Anno.AnnLineComment a+pattern EpaBlockComment a = Anno.AnnBlockComment a #endif
src/Development/IDE/GHC/ExactPrint.hs view
@@ -23,7 +23,6 @@       transformM,       ExactPrint(..), #if !MIN_VERSION_ghc(9,2,0)-      useAnnotatedSource,       Anns,       Annotate,       setPrecedingLinesT,@@ -44,6 +43,7 @@       ASTElement (..),       ExceptStringT (..),       TransformT,+      Log(..),     ) where @@ -67,13 +67,18 @@ import           Data.Traversable                        (for) import           Development.IDE.Core.RuleTypes import           Development.IDE.Core.Service            (runAction)-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Shake              hiding (Log)+import qualified Development.IDE.Core.Shake              as Shake import           Development.IDE.GHC.Compat              hiding (parseImport,                                                           parsePattern,                                                           parseType) import           Development.IDE.Graph                   (RuleResult, Rules) import           Development.IDE.Graph.Classes import           Development.IDE.Types.Location+import           Development.IDE.Types.Logger            (Pretty (pretty),+                                                          Recorder,+                                                          WithPriority,+                                                          cmapWithPrio) import qualified GHC.Generics                            as GHC import           Generics.SYB import           Generics.SYB.GHC@@ -101,6 +106,12 @@  ------------------------------------------------------------------------------ +data Log = LogShake Shake.Log deriving Show++instance Pretty Log where+  pretty = \case+    LogShake shakeLog -> pretty shakeLog+ data GetAnnotatedParsedSource = GetAnnotatedParsedSource   deriving (Eq, Show, Typeable, GHC.Generic) @@ -109,8 +120,8 @@ type instance RuleResult GetAnnotatedParsedSource = Annotated ParsedSource  -- | Get the latest version of the annotated parse source with comments.-getAnnotatedParsedSourceRule :: Rules ()-getAnnotatedParsedSourceRule = define $ \GetAnnotatedParsedSource nfp -> do+getAnnotatedParsedSourceRule :: Recorder (WithPriority Log) -> Rules ()+getAnnotatedParsedSourceRule recorder = define (cmapWithPrio LogShake recorder) $ \GetAnnotatedParsedSource nfp -> do   pm <- use GetParsedModuleWithComments nfp   return ([], fmap annotateParsedSource pm) @@ -120,16 +131,6 @@ #else annotateParsedSource :: ParsedModule -> Annotated ParsedSource annotateParsedSource = fixAnns-#endif--#if !MIN_VERSION_ghc(9,2,0)-useAnnotatedSource ::-  String ->-  IdeState ->-  NormalizedFilePath ->-  IO (Maybe (Annotated ParsedSource))-useAnnotatedSource herald state nfp =-    runAction herald state (use GetAnnotatedParsedSource nfp) #endif  ------------------------------------------------------------------------------
src/Development/IDE/GHC/Orphans.hs view
@@ -39,24 +39,35 @@ import           Data.Bifunctor             (Bifunctor (..)) import           Data.Hashable import           Data.String                (IsString (fromString))+import           Data.Text                  (unpack)+#if MIN_VERSION_ghc(9,0,0)+import          GHC.ByteCode.Types+#else+import          ByteCodeTypes+#endif  -- Orphan instances for types from the GHC API.-instance Show CoreModule where show = prettyPrint+instance Show CoreModule where show = unpack . printOutputable instance NFData CoreModule where rnf = rwhnf-instance Show CgGuts where show = prettyPrint . cg_module+instance Show CgGuts where show = unpack . printOutputable . cg_module instance NFData CgGuts where rnf = rwhnf instance Show ModDetails where show = const "<moddetails>" instance NFData ModDetails where rnf = rwhnf instance NFData SafeHaskellMode where rnf = rwhnf-instance Show Linkable where show = prettyPrint-instance NFData Linkable where rnf = rwhnf-instance Show PackageFlag where show = prettyPrint-instance Show InteractiveImport where show = prettyPrint-instance Show PackageName  where show = prettyPrint+instance Show Linkable where show = unpack . printOutputable+instance NFData Linkable where rnf (LM a b c) = rnf a `seq` rnf b `seq` rnf c+instance NFData Unlinked where+  rnf (DotO f) = rnf f+  rnf (DotA f) = rnf f+  rnf (DotDLL f) = rnf f+  rnf (BCOs a b) = seqCompiledByteCode a `seq` liftRnf rwhnf b+instance Show PackageFlag where show = unpack . printOutputable+instance Show InteractiveImport where show = unpack . printOutputable+instance Show PackageName  where show = unpack . printOutputable  #if !MIN_VERSION_ghc(9,0,1)-instance Show ComponentId  where show = prettyPrint-instance Show SourcePackageId  where show = prettyPrint+instance Show ComponentId  where show = unpack . printOutputable+instance Show SourcePackageId  where show = unpack . printOutputable  instance Show GhcPlugins.InstalledUnitId where     show = installedUnitIdString@@ -66,7 +77,7 @@ instance Hashable GhcPlugins.InstalledUnitId where   hashWithSalt salt = hashWithSalt salt . installedUnitIdString #else-instance Show UnitId where show = prettyPrint+instance Show UnitId where show = unpack . printOutputable deriving instance Ord SrcSpan deriving instance Ord UnhelpfulSpanReason #endif@@ -76,7 +87,7 @@ instance Show Module where     show = moduleNameString . moduleName -instance Outputable a => Show (GenLocated SrcSpan a) where show = prettyPrint+instance Outputable a => Show (GenLocated SrcSpan a) where show = unpack . printOutputable  instance (NFData l, NFData e) => NFData (GenLocated l e) where     rnf (L l e) = rnf l `seq` rnf e@@ -197,5 +208,5 @@ #endif   rnf = rwhnf -instance Show OccName where show = prettyPrint+instance Show OccName where show = unpack . printOutputable instance Hashable OccName where hashWithSalt s n = hashWithSalt s (getKey $ getUnique n)
src/Development/IDE/GHC/Util.hs view
@@ -7,8 +7,6 @@     modifyDynFlags,     evalGhcEnv,     -- * GHC wrappers-    prettyPrint,-    unsafePrintSDoc,     printRdrName,     Development.IDE.GHC.Util.printName,     ParseResult(..), runParser,@@ -28,7 +26,9 @@     setHieDir,     dontWriteHieFiles,     disableWarningsAsErrors,-    traceAst) where+    traceAst,+    printOutputable+    ) where  #if MIN_VERSION_ghc(9,2,0) import           GHC.Data.FastString@@ -130,16 +130,9 @@ bytestringToStringBuffer :: ByteString -> StringBuffer bytestringToStringBuffer (PS buf cur len) = StringBuffer{..} --- | Pretty print a GHC value using 'unsafeGlobalDynFlags '.-prettyPrint :: Outputable a => a -> String-prettyPrint = unsafePrintSDoc . ppr--unsafePrintSDoc :: SDoc -> String-unsafePrintSDoc sdoc = showSDocUnsafe sdoc- -- | Pretty print a 'RdrName' wrapping operators in parens printRdrName :: RdrName -> String-printRdrName name = showSDocUnsafe $ parenSymOcc rn (ppr rn)+printRdrName name = T.unpack $ printOutputable $ parenSymOcc rn (ppr rn)   where     rn = rdrNameOcc name @@ -304,7 +297,7 @@ #if MIN_VERSION_ghc(9,2,0)     renderDump = renderWithContext defaultSDocContext{sdocStyle = defaultDumpStyle, sdocPprDebug = True} #else-    renderDump = unsafePrintSDoc+    renderDump = showSDocUnsafe . ppr #endif     htmlDump = showAstDataHtml x     doTrace = unsafePerformIO $ do@@ -318,4 +311,18 @@ #endif             , "file://" ++ htmlDumpFileName] +-- Should in `Development.IDE.GHC.Orphans`,+-- leave it here to prevent cyclic module dependency+#if !MIN_VERSION_ghc(8,10,0)+instance Outputable SDoc where+  ppr = id+#endif +-- | Print a GHC value in `defaultUserStyle` without unique symbols.+--+-- This is the most common print utility, will print with a user-friendly style like: `a_a4ME` as `a`.+--+-- It internal using `showSDocUnsafe` with `unsafeGlobalDynFlags`.+printOutputable :: Outputable a => a -> T.Text+printOutputable = T.pack . printWithoutUniques+{-# INLINE printOutputable #-}
src/Development/IDE/Import/DependencyInformation.hs view
@@ -129,6 +129,7 @@     -- need to add edges between .hs-boot and .hs so that the .hs files     -- appear later in the sort.     , rawBootMap   :: !BootIdMap+    , rawModuleNameMap :: !(FilePathIdMap ShowableModuleName)     } deriving Show  data DependencyInformation =@@ -220,15 +221,12 @@     { depErrorNodes = IntMap.fromList errorNodes     , depModuleDeps = moduleDeps     , depReverseModuleDeps = reverseModuleDeps-    , depModuleNames = IntMap.fromList $ coerce moduleNames+    , depModuleNames = rawModuleNameMap     , depPathIdMap = rawPathIdMap     , depBootMap = rawBootMap     }   where resultGraph = buildResultGraph rawImports         (errorNodes, successNodes) = partitionNodeResults $ IntMap.toList resultGraph-        moduleNames :: [(FilePathId, ModuleName)]-        moduleNames =-          [ (fId, modName) | (_, imports) <- successNodes, (L _ modName, fId) <- imports]         successEdges :: [(FilePathId, [FilePathId])]         successEdges =             map
src/Development/IDE/LSP/LanguageServer.hs view
@@ -10,6 +10,7 @@ -- This version removes the daml: handling module Development.IDE.LSP.LanguageServer     ( runLanguageServer+    , Log(..)     ) where  import           Control.Concurrent.STM@@ -31,35 +32,61 @@ import           UnliftIO.Directory import           UnliftIO.Exception -import           Development.IDE.Core.FileStore import           Development.IDE.Core.IdeConfiguration-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Shake            hiding (Log) import           Development.IDE.Core.Tracing import           Development.IDE.LSP.HoverDefinition import           Development.IDE.Types.Logger  import           Control.Monad.IO.Unlift               (MonadUnliftIO)+import qualified Development.IDE.Session               as Session+import qualified Development.IDE.Types.Logger          as Logger import           Development.IDE.Types.Shake           (WithHieDb) import           System.IO.Unsafe                      (unsafeInterleaveIO) -issueTrackerUrl :: T.Text-issueTrackerUrl = "https://github.com/haskell/haskell-language-server/issues"+data Log+  = LogRegisteringIdeConfig !IdeConfiguration+  | LogReactorThreadException !SomeException+  | LogReactorMessageActionException !SomeException+  | LogReactorThreadStopped+  | LogCancelledRequest !SomeLspId+  | LogSession Session.Log+  deriving Show +instance Pretty Log where+  pretty = \case+    LogRegisteringIdeConfig ideConfig ->+      "Registering IDE configuration:" <+> viaShow ideConfig+    LogReactorThreadException e ->+      vcat+        [ "ReactorThreadException"+        , pretty $ displayException e ]+    LogReactorMessageActionException e ->+      vcat+        [ "ReactorMessageActionException"+        , pretty $ displayException e ]+    LogReactorThreadStopped ->+      "Reactor thread stopped"+    LogCancelledRequest requestId ->+      "Cancelled request" <+> viaShow requestId+    LogSession log -> pretty log+ -- used to smuggle RankNType WithHieDb through dbMVar newtype WithHieDbShield = WithHieDbShield WithHieDb  runLanguageServer     :: forall config. (Show config)-    => LSP.Options+    => Recorder (WithPriority Log)+    -> LSP.Options     -> Handle -- input     -> Handle -- output     -> (FilePath -> IO FilePath) -- ^ Map root paths to the location of the hiedb for the project     -> config     -> (config -> Value -> Either T.Text config)     -> LSP.Handlers (ServerM config)-    -> (LSP.LanguageContextEnv config -> VFSHandle -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState)+    -> (LSP.LanguageContextEnv config -> Maybe FilePath -> WithHieDb -> IndexQueue -> IO IdeState)     -> IO ()-runLanguageServer options inH outH getHieDbLoc defaultConfig onConfigurationChange userHandlers getIdeState = do+runLanguageServer recorder options inH outH getHieDbLoc defaultConfig onConfigurationChange userHandlers getIdeState = do      -- This MVar becomes full when the server thread exits or we receive exit message from client.     -- LSP server will be canceled when it's full.@@ -128,6 +155,9 @@             serverDefinition      where+        log :: Logger.Priority -> Log -> IO ()+        log = logWith recorder+         handleInit           :: MVar () -> IO () -> (SomeLspId -> IO ()) -> (SomeLspId -> IO ()) -> Chan ReactorMessage           -> LSP.LanguageContextEnv config -> RequestMessage Initialize -> IO (Either err (LSP.LanguageContextEnv config, IdeState))@@ -142,33 +172,20 @@             dbMVar <- newEmptyMVar             ~(WithHieDbShield withHieDb,hieChan) <- unsafeInterleaveIO $ takeMVar dbMVar -            ide <- getIdeState env (makeLSPVFSHandle env) root withHieDb hieChan+            ide <- getIdeState env root withHieDb hieChan              let initConfig = parseConfiguration params-            logInfo (ideLogger ide) $ T.pack $ "Registering ide configuration: " <> show initConfig++            log Info $ LogRegisteringIdeConfig initConfig             registerIdeConfiguration (shakeExtras ide) initConfig              let handleServerException (Left e) = do-                    logError logger $-                        T.pack $ "Fatal error in server thread: " <> show e-                    sendErrorMessage e+                    log Error $ LogReactorThreadException e                     exitClientMsg                 handleServerException (Right _) = pure () -                sendErrorMessage (e :: SomeException) = do-                    LSP.runLspT env $ LSP.sendNotification SWindowShowMessage $-                        ShowMessageParams MtError $ T.unlines-                        [ "Unhandled exception, please [report](" <> issueTrackerUrl <> "): "-                        , T.pack(show e)-                        ]-                 exceptionInHandler e = do-                    logError logger $ T.pack $-                        "Unexpected exception, please report!\n" ++-                        "Exception: " ++ show e-                    sendErrorMessage e--                logger = ideLogger ide+                    log Error $ LogReactorMessageActionException e                  checkCancelled _id act k =                     flip finally (clearReqId _id) $@@ -180,14 +197,14 @@                             cancelOrRes <- race (waitForCancel _id) act                             case cancelOrRes of                                 Left () -> do-                                    logDebug (ideLogger ide) $ T.pack $ "Cancelled request " <> show _id+                                    log Debug $ LogCancelledRequest _id                                     k $ ResponseError RequestCancelled "" Nothing                                 Right res -> pure res                         ) $ \(e :: SomeException) -> do                             exceptionInHandler e                             k $ ResponseError InternalError (T.pack $ show e) Nothing             _ <- flip forkFinally handleServerException $ do-                untilMVar lifetime $ runWithDb logger dbLoc $ \withHieDb hieChan -> do+                untilMVar lifetime $ runWithDb (cmapWithPrio LogSession recorder) dbLoc $ \withHieDb hieChan -> do                     putMVar dbMVar (WithHieDbShield withHieDb,hieChan)                     forever $ do                         msg <- readChan clientMsgChan@@ -196,7 +213,7 @@                         case msg of                             ReactorNotification act -> handle exceptionInHandler act                             ReactorRequest _id act k -> void $ async $ checkCancelled _id act k-                logInfo logger "Reactor thread stopped"+                log Info LogReactorThreadStopped             pure $ Right (env,ide)  
src/Development/IDE/LSP/Notifications.hs view
@@ -9,6 +9,7 @@ module Development.IDE.LSP.Notifications     ( whenUriFile     , descriptor+    , Log(..)     ) where  import           Language.LSP.Types@@ -26,57 +27,69 @@                                                         resetFileStore,                                                         setFileModified,                                                         setSomethingModified)+import qualified Development.IDE.Core.FileStore        as FileStore import           Development.IDE.Core.IdeConfiguration-import           Development.IDE.Core.OfInterest+import           Development.IDE.Core.OfInterest       hiding (Log, LogShake) import           Development.IDE.Core.RuleTypes        (GetClientSettings (..))-import           Development.IDE.Core.Service-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Service          hiding (Log, LogShake)+import           Development.IDE.Core.Shake            hiding (Log, Priority)+import qualified Development.IDE.Core.Shake            as Shake import           Development.IDE.Types.Location import           Development.IDE.Types.Logger import           Development.IDE.Types.Shake           (toKey) import           Ide.Types +data Log+  = LogShake Shake.Log+  | LogFileStore FileStore.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogShake log     -> pretty log+    LogFileStore log -> pretty log+ whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO () whenUriFile uri act = whenJust (LSP.uriToFilePath uri) $ act . toNormalizedFilePath' -descriptor :: PluginId -> PluginDescriptor IdeState-descriptor plId = (defaultPluginDescriptor plId) { pluginNotificationHandlers = mconcat+descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState+descriptor recorder plId = (defaultPluginDescriptor plId) { pluginNotificationHandlers = mconcat   [ mkPluginNotificationHandler LSP.STextDocumentDidOpen $-      \ide _ (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> liftIO $ do+      \ide vfs _ (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> liftIO $ do       atomically $ 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           addFileOfInterest ide file Modified{firstOpen=True}-          setFileModified ide False file+          setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide False file           logDebug (ideLogger ide) $ "Opened text document: " <> getUri _uri    , mkPluginNotificationHandler LSP.STextDocumentDidChange $-      \ide _ (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> liftIO $ do+      \ide vfs _ (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> liftIO $ do         atomically $ updatePositionMapping ide identifier changes         whenUriFile _uri $ \file -> do           addFileOfInterest ide file Modified{firstOpen=False}-          setFileModified ide False file+          setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide False file         logDebug (ideLogger ide) $ "Modified text document: " <> getUri _uri    , mkPluginNotificationHandler LSP.STextDocumentDidSave $-      \ide _ (DidSaveTextDocumentParams TextDocumentIdentifier{_uri} _) -> liftIO $ do+      \ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier{_uri} _) -> liftIO $ do         whenUriFile _uri $ \file -> do             addFileOfInterest ide file OnDisk-            setFileModified ide True file+            setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide True file         logDebug (ideLogger ide) $ "Saved text document: " <> getUri _uri    , mkPluginNotificationHandler LSP.STextDocumentDidClose $-        \ide _ (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> liftIO $ do+        \ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> liftIO $ do           whenUriFile _uri $ \file -> do               deleteFileOfInterest ide file               let msg = "Closed text document: " <> getUri _uri               scheduleGarbageCollection ide-              setSomethingModified ide [] $ Text.unpack msg+              setSomethingModified (VFSModified vfs) ide [] $ Text.unpack msg               logDebug (ideLogger ide) msg    , mkPluginNotificationHandler LSP.SWorkspaceDidChangeWatchedFiles $-      \ide _ (DidChangeWatchedFilesParams (List fileEvents)) -> liftIO $ do+      \ide vfs _ (DidChangeWatchedFilesParams (List 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         -- filter out files of interest, since we already know all about those@@ -93,10 +106,10 @@             logDebug (ideLogger ide) $ "Watched file events: " <> Text.pack msg             modifyFileExists ide fileEvents'             resetFileStore ide fileEvents'-            setSomethingModified ide [] msg+            setSomethingModified (VFSModified vfs) ide [] msg    , mkPluginNotificationHandler LSP.SWorkspaceDidChangeWorkspaceFolders $-      \ide _ (DidChangeWorkspaceFoldersParams events) -> liftIO $ do+      \ide _ _ (DidChangeWorkspaceFoldersParams events) -> liftIO $ do         let add       = S.union             substract = flip S.difference         modifyWorkspaceFolders ide@@ -104,15 +117,15 @@           . substract (foldMap (S.singleton . parseWorkspaceFolder) (_removed events))    , mkPluginNotificationHandler LSP.SWorkspaceDidChangeConfiguration $-      \ide _ (DidChangeConfigurationParams cfg) -> liftIO $ do+      \ide vfs _ (DidChangeConfigurationParams cfg) -> liftIO $ do         let msg = Text.pack $ show cfg         logDebug (ideLogger ide) $ "Configuration changed: " <> msg         modifyClientSettings ide (const $ Just cfg)-        setSomethingModified ide [toKey GetClientSettings emptyFilePath] "config change"+        setSomethingModified (VFSModified vfs) ide [toKey GetClientSettings emptyFilePath] "config change" -  , mkPluginNotificationHandler LSP.SInitialized $ \ide _ _ -> do+  , mkPluginNotificationHandler LSP.SInitialized $ \ide _ _ _ -> do       --------- Initialize Shake session ---------------------------------------------------------------------      liftIO $ shakeSessionInit ide+      liftIO $ shakeSessionInit (cmapWithPrio LogShake recorder) ide        --------- Set up file watchers ------------------------------------------------------------------------       opts <- liftIO $ getIdeOptionsIO $ shakeExtras ide
src/Development/IDE/LSP/Outline.hs view
@@ -13,7 +13,6 @@ import           Data.Functor import           Data.Generics import           Data.Maybe-import           Data.Text                      (Text, pack) import qualified Data.Text                      as T import           Development.IDE.Core.Rules import           Development.IDE.Core.Shake@@ -21,6 +20,7 @@ import           Development.IDE.GHC.Error      (rangeToRealSrcSpan,                                                  realSrcSpanToRange) import           Development.IDE.Types.Location+import           Development.IDE.GHC.Util       (printOutputable) import           Language.LSP.Server            (LspM) import           Language.LSP.Types             (DocumentSymbol (..),                                                  DocumentSymbolParams (DocumentSymbolParams, _textDocument),@@ -47,7 +47,7 @@                moduleSymbol = hsmodName >>= \case                  (L (locA -> (RealSrcSpan l _)) m) -> Just $                    (defDocumentSymbol l :: DocumentSymbol)-                     { _name  = pprText m+                     { _name  = printOutputable m                      , _kind  = SkFile                      , _range = Range (Position 0 0) (Position maxBound 0) -- _ltop is 0 0 0 0                      }@@ -70,18 +70,18 @@ documentSymbolForDecl :: LHsDecl GhcPs -> Maybe DocumentSymbol documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ FamDecl { tcdFam = FamilyDecl { fdLName = L _ n, fdInfo, fdTyVars } }))   = Just (defDocumentSymbol l :: DocumentSymbol)-    { _name   = showRdrName n-                  <> (case pprText fdTyVars of+    { _name   = printOutputable n+                  <> (case printOutputable fdTyVars of                        "" -> ""                        t  -> " " <> t                      )-    , _detail = Just $ pprText fdInfo+    , _detail = Just $ printOutputable fdInfo     , _kind   = SkFunction     } documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ ClassDecl { tcdLName = L _ name, tcdSigs, tcdTyVars }))   = Just (defDocumentSymbol l :: DocumentSymbol)-    { _name     = showRdrName name-                    <> (case pprText tcdTyVars of+    { _name     = printOutputable name+                    <> (case printOutputable tcdTyVars of                          "" -> ""                          t  -> " " <> t                        )@@ -90,7 +90,7 @@     , _children =       Just $ List         [ (defDocumentSymbol l :: DocumentSymbol)-            { _name           = showRdrName n+            { _name           = printOutputable n             , _kind           = SkMethod             , _selectionRange = realSrcSpanToRange l'             }@@ -100,12 +100,12 @@     } documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ DataDecl { tcdLName = L _ name, tcdDataDefn = HsDataDefn { dd_cons } }))   = Just (defDocumentSymbol l :: DocumentSymbol)-    { _name     = showRdrName name+    { _name     = printOutputable name     , _kind     = SkStruct     , _children =       Just $ List         [ (defDocumentSymbol l :: DocumentSymbol)-            { _name           = showRdrName n+            { _name           = printOutputable n             , _kind           = SkConstructor             , _selectionRange = realSrcSpanToRange l' #if MIN_VERSION_ghc(9,2,0)@@ -123,7 +123,7 @@   where     cvtFld :: LFieldOcc GhcPs -> Maybe DocumentSymbol     cvtFld (L (RealSrcSpan l _) n) = Just $ (defDocumentSymbol l :: DocumentSymbol)-                { _name = showRdrName (unLoc (rdrNameFieldOcc n))+                { _name = printOutputable (unLoc (rdrNameFieldOcc n))                 , _kind = SkField                 }     cvtFld _  = Nothing@@ -138,7 +138,7 @@     -- | Extract the record fields of a constructor     conArgRecordFields (RecCon (L _ lcdfs)) = Just $ List       [ (defDocumentSymbol l :: DocumentSymbol)-          { _name = showRdrName n+          { _name = printOutputable n           , _kind = SkField           }       | L _ cdf <- lcdfs@@ -147,12 +147,12 @@     conArgRecordFields _ = Nothing #endif documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (TyClD _ SynDecl { tcdLName = L (locA -> (RealSrcSpan l' _)) n })) = Just-  (defDocumentSymbol l :: DocumentSymbol) { _name           = showRdrName n+  (defDocumentSymbol l :: DocumentSymbol) { _name           = printOutputable n                                           , _kind           = SkTypeParameter                                           , _selectionRange = realSrcSpanToRange l'                                           } documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (InstD _ ClsInstD { cid_inst = ClsInstDecl { cid_poly_ty } }))-  = Just (defDocumentSymbol l :: DocumentSymbol) { _name = pprText cid_poly_ty+  = Just (defDocumentSymbol l :: DocumentSymbol) { _name = printOutputable cid_poly_ty                                                  , _kind = SkInterface                                                  } #if MIN_VERSION_ghc(9,2,0)@@ -161,8 +161,8 @@ documentSymbolForDecl (L (RealSrcSpan l _) (InstD _ DataFamInstD { dfid_inst = DataFamInstDecl HsIB { hsib_body = FamEqn { feqn_tycon, feqn_pats } } })) #endif   = Just (defDocumentSymbol l :: DocumentSymbol)-    { _name = showRdrName (unLoc feqn_tycon) <> " " <> T.unwords-                (map pprText feqn_pats)+    { _name = printOutputable (unLoc feqn_tycon) <> " " <> T.unwords+                (map printOutputable feqn_pats)     , _kind = SkInterface     } #if MIN_VERSION_ghc(9,2,0)@@ -171,24 +171,24 @@ documentSymbolForDecl (L (RealSrcSpan l _) (InstD _ TyFamInstD { tfid_inst = TyFamInstDecl HsIB { hsib_body = FamEqn { feqn_tycon, feqn_pats } } })) #endif   = Just (defDocumentSymbol l :: DocumentSymbol)-    { _name = showRdrName (unLoc feqn_tycon) <> " " <> T.unwords-                (map pprText feqn_pats)+    { _name = printOutputable (unLoc feqn_tycon) <> " " <> T.unwords+                (map printOutputable feqn_pats)     , _kind = SkInterface     } documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (DerivD _ DerivDecl { deriv_type })) =   gfindtype deriv_type <&> \(L (_ :: SrcSpan) name) ->-    (defDocumentSymbol l :: DocumentSymbol) { _name = pprText @(HsType GhcPs)+    (defDocumentSymbol l :: DocumentSymbol) { _name = printOutputable @(HsType GhcPs)                                               name                                             , _kind = SkInterface                                             } documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (ValD _ FunBind{fun_id = L _ name})) = Just     (defDocumentSymbol l :: DocumentSymbol)-      { _name   = showRdrName name+      { _name   = printOutputable name       , _kind   = SkFunction       } documentSymbolForDecl (L (locA -> (RealSrcSpan l _)) (ValD _ PatBind{pat_lhs})) = Just     (defDocumentSymbol l :: DocumentSymbol)-      { _name   = pprText pat_lhs+      { _name   = printOutputable pat_lhs       , _kind   = SkFunction       } @@ -204,7 +204,7 @@                   ForeignExport{} -> Just "export"                   XForeignDecl{}  -> Nothing     }-  where name = showRdrName $ unLoc $ fd_name x+  where name = printOutputable $ unLoc $ fd_name x  documentSymbolForDecl _ = Nothing @@ -228,7 +228,7 @@ documentSymbolForImport :: LImportDecl GhcPs -> Maybe DocumentSymbol documentSymbolForImport (L (locA -> (RealSrcSpan l _)) ImportDecl { ideclName, ideclQualified }) = Just   (defDocumentSymbol l :: DocumentSymbol)-    { _name   = "import " <> pprText ideclName+    { _name   = "import " <> printOutputable ideclName     , _kind   = SkModule #if MIN_VERSION_ghc(8,10,0)     , _detail = case ideclQualified of { NotQualified -> Nothing; _ -> Just "qualified" }@@ -248,12 +248,6 @@   _selectionRange = realSrcSpanToRange l   _children       = Nothing   _tags           = Nothing--showRdrName :: RdrName -> Text-showRdrName = pprText--pprText :: Outputable a => a -> Text-pprText = pack . showSDocUnsafe . ppr  -- the version of getConNames for ghc9 is restricted to only the renaming phase #if !MIN_VERSION_ghc(9,2,0)
src/Development/IDE/LSP/Server.hs view
@@ -22,6 +22,7 @@ import           Language.LSP.Server          (Handlers, LspM) import qualified Language.LSP.Server          as LSP import           Language.LSP.Types+import           Language.LSP.VFS import           UnliftIO.Chan  data ReactorMessage@@ -48,14 +49,16 @@ notificationHandler   :: forall (m :: Method FromClient Notification) c. (HasTracing (MessageParams m)) =>      SMethod m-  -> (IdeState -> MessageParams m -> LspM c ())+  -> (IdeState -> VFS -> MessageParams m -> LspM c ())   -> Handlers (ServerM c) notificationHandler m k = LSP.notificationHandler m $ \NotificationMessage{_params,_method}-> do   (chan,ide) <- ask   env <- LSP.getLspEnv+  -- Take a snapshot of the VFS state on every notification+  -- We only need to do this here because the VFS state is only updated+  -- on notifications+  vfs <- LSP.getVirtualFiles   let trace x = otTracedHandler "Notification" (show _method) $ \sp -> do         traceWithSpan sp _params         x-  writeChan chan $ ReactorNotification (trace $ LSP.runLspT env $ k ide _params)--+  writeChan chan $ ReactorNotification (trace $ LSP.runLspT env $ k ide vfs _params)
src/Development/IDE/Main.hs view
@@ -8,13 +8,14 @@ ,isLSP ,commandP ,defaultMain-,testing) where-import           Control.Concurrent.Extra              (newLock, withLock,-                                                        withNumCapabilities)+,testing+,Log(..)+) where+import           Control.Concurrent.Extra              (withNumCapabilities) import           Control.Concurrent.STM.Stats          (atomically,                                                         dumpSTMStats)-import           Control.Exception.Safe                (Exception (displayException),-                                                        catchAny)+import           Control.Exception.Safe                (SomeException, catchAny,+                                                        displayException) import           Control.Monad.Extra                   (concatMapM, unless,                                                         when) import qualified Data.Aeson.Encode.Pretty              as A@@ -26,18 +27,16 @@                                                         nub, nubOrd, partition) import           Data.Maybe                            (catMaybes, isJust) import qualified Data.Text                             as T-import qualified Data.Text.IO                          as T import           Data.Text.Lazy.Encoding               (decodeUtf8) import qualified Data.Text.Lazy.IO                     as LT import           Data.Typeable                         (typeOf) import           Development.IDE                       (Action, GhcVersion (..),-                                                        Priority (Debug), Rules,+                                                        Priority (Debug, Error), Rules,                                                         ghcVersion,                                                         hDuplicateTo') import           Development.IDE.Core.Debouncer        (Debouncer,                                                         newAsyncDebouncer)-import           Development.IDE.Core.FileStore        (isWatchSupported,-                                                        makeVFSHandle)+import           Development.IDE.Core.FileStore        (isWatchSupported) import           Development.IDE.Core.IdeConfiguration (IdeConfiguration (..),                                                         registerIdeConfiguration) import           Development.IDE.Core.OfInterest       (FileOfInterestStatus (OnDisk),@@ -50,17 +49,23 @@                                                         TypeCheck (TypeCheck)) import           Development.IDE.Core.Rules            (GhcSessionIO (GhcSessionIO),                                                         mainRule)+import qualified Development.IDE.Core.Rules            as Rules import           Development.IDE.Core.Service          (initialise, runAction)+import qualified Development.IDE.Core.Service          as Service import           Development.IDE.Core.Shake            (IdeState (shakeExtras),                                                         ShakeExtras (state),                                                         shakeSessionInit, uses)+import qualified Development.IDE.Core.Shake            as Shake import           Development.IDE.Core.Tracing          (measureMemory) import           Development.IDE.Graph                 (action) import           Development.IDE.LSP.LanguageServer    (runLanguageServer)+import qualified Development.IDE.LSP.LanguageServer    as LanguageServer import           Development.IDE.Main.HeapStats        (withHeapStats)+import qualified Development.IDE.Main.HeapStats        as HeapStats import           Development.IDE.Plugin                (Plugin (pluginHandlers, pluginModifyDynflags, pluginRules)) import           Development.IDE.Plugin.HLS            (asGhcIdePlugin)-import qualified Development.IDE.Plugin.HLS.GhcIde     as Ghcide+import qualified Development.IDE.Plugin.HLS            as PluginHLS+import qualified Development.IDE.Plugin.HLS.GhcIde     as GhcIde import qualified Development.IDE.Plugin.Test           as Test import           Development.IDE.Session               (SessionLoadingOptions,                                                         getHieDbLoc,@@ -68,11 +73,14 @@                                                         retryOnSqliteBusy,                                                         runWithDb,                                                         setInitialDynFlags)+import qualified Development.IDE.Session               as Session import           Development.IDE.Types.Location        (NormalizedUri,                                                         toNormalizedFilePath')-import           Development.IDE.Types.Logger          (Logger (Logger),-                                                        Priority (Info),-                                                        logDebug, logInfo)+import           Development.IDE.Types.Logger          (Logger, Pretty (pretty),+                                                        Priority (Info, Warning),+                                                        Recorder, WithPriority,+                                                        cmapWithPrio, logWith,+                                                        vsep, (<+>)) import           Development.IDE.Types.Options         (IdeGhcSession,                                                         IdeOptions (optCheckParents, optCheckProject, optReportProgress, optRunSubset),                                                         IdeTesting (IdeTesting),@@ -118,10 +126,49 @@                                                         hSetEncoding, stderr,                                                         stdin, stdout, utf8) import           System.Random                         (newStdGen)-import           System.Time.Extra                     (offsetTime,+import           System.Time.Extra                     (Seconds, offsetTime,                                                         showDuration) import           Text.Printf                           (printf) +data Log+  = LogHeapStats !HeapStats.Log+  | LogLspStart+  | LogLspStartDuration !Seconds+  | LogShouldRunSubset !Bool+  | LogOnlyPartialGhc9Support+  | LogSetInitialDynFlagsException !SomeException+  | LogService Service.Log+  | LogShake Shake.Log+  | LogGhcIde GhcIde.Log+  | LogLanguageServer LanguageServer.Log+  | LogSession Session.Log+  | LogPluginHLS PluginHLS.Log+  | LogRules Rules.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogHeapStats log -> pretty log+    LogLspStart ->+      vsep+        [ "Staring LSP server..."+        , "If you are seeing this in a terminal, you probably should have run WITHOUT the --lsp option!"]+    LogLspStartDuration duration ->+      "Started LSP server in" <+> pretty (showDuration duration)+    LogShouldRunSubset shouldRunSubset ->+      "shouldRunSubset:" <+> pretty shouldRunSubset+    LogOnlyPartialGhc9Support ->+      "Currently, HLS supports GHC 9 only partially. See [issue #297](https://github.com/haskell/haskell-language-server/issues/297) for more detail."+    LogSetInitialDynFlagsException e ->+      "setInitialDynFlags:" <+> pretty (displayException e)+    LogService log -> pretty log+    LogShake log -> pretty log+    LogGhcIde log -> pretty log+    LogLanguageServer log -> pretty log+    LogSession log -> pretty log+    LogPluginHLS log -> pretty log+    LogRules log -> pretty log+ data Command     = Check [FilePath]  -- ^ Typecheck some paths and print diagnostics. Exit code is the number of failures     | Db {hieOptions ::  HieDb.Options, hieCommand :: HieDb.Command}@@ -132,7 +179,6 @@     | Custom {ideCommand :: IdeCommand IdeState} -- ^ User defined     deriving Show - -- TODO move these to hiedb deriving instance Show HieDb.Command deriving instance Show HieDb.Options@@ -144,8 +190,8 @@ commandP :: IdePlugins IdeState -> Parser Command commandP plugins =     hsubparser(command "typecheck" (info (Check <$> fileCmd) fileInfo)-            <> command "hiedb" (info (Db <$> HieDb.optParser "" True <*> HieDb.cmdParser <**> helper) hieInfo)-            <> command "lsp" (info (pure LSP <**> helper) lspInfo)+            <> command "hiedb" (info (Db <$> HieDb.optParser "" True <*> HieDb.cmdParser) hieInfo)+            <> command "lsp" (info (pure LSP) lspInfo)             <> command "vscode-extension-schema" extensionSchemaCommand             <> command "generate-default-config" generateDefaultConfigCommand             <> pluginCommands@@ -187,18 +233,16 @@     , argsThreads               :: Maybe Natural     } -instance Default Arguments where-    def = defaultArguments Info -defaultArguments :: Priority -> Arguments-defaultArguments priority = Arguments+defaultArguments :: Recorder (WithPriority Log) -> Logger -> Arguments+defaultArguments recorder logger = Arguments         { argsProjectRoot = Nothing         , argsOTMemoryProfiling = False         , argCommand = LSP-        , argsLogger = stderrLogger priority-        , argsRules = mainRule def >> action kick+        , argsLogger = pure logger+        , argsRules = mainRule (cmapWithPrio LogRules recorder) def >> action kick         , argsGhcidePlugin = mempty-        , argsHlsPlugins = pluginDescToIdePlugins Ghcide.descriptors+        , argsHlsPlugins = pluginDescToIdePlugins (GhcIde.descriptors (cmapWithPrio LogGhcIde recorder))         , argsSessionLoadingOptions = def         , argsIdeOptions = \config ghcSession -> (defaultIdeOptions ghcSession)             { optCheckProject = pure $ checkProject config@@ -226,35 +270,39 @@                 return newStdout         } -testing :: Arguments-testing = (defaultArguments Debug) {-    argsHlsPlugins = pluginDescToIdePlugins $-        idePluginsToPluginDesc (argsHlsPlugins def)-        ++ [Test.blockCommandDescriptor "block-command", Test.plugin],-    argsIdeOptions = \config sessionLoader ->-            let defOptions = argsIdeOptions def config sessionLoader-            in defOptions {-                optTesting = IdeTesting True-            }-} --- | Cheap stderr logger that relies on LineBuffering-stderrLogger :: Priority -> IO Logger-stderrLogger logLevel = do-    lock <- newLock-    return $ Logger $ \p m -> when (p >= logLevel) $ withLock lock $-        T.hPutStrLn stderr $ "[" <> T.pack (show p) <> "] " <> m+testing :: Recorder (WithPriority Log) -> Logger -> Arguments+testing recorder logger =+  let+    arguments@Arguments{ argsHlsPlugins, argsIdeOptions } = defaultArguments recorder logger+    hlsPlugins = pluginDescToIdePlugins $+      idePluginsToPluginDesc argsHlsPlugins+      ++ [Test.blockCommandDescriptor "block-command", Test.plugin]+    ideOptions = \config sessionLoader ->+      let+        defOptions = argsIdeOptions config sessionLoader+      in+        defOptions{ optTesting = IdeTesting True }+  in+    arguments+      { argsHlsPlugins = hlsPlugins+      , argsIdeOptions = ideOptions+      } -defaultMain :: Arguments -> IO ()-defaultMain Arguments{..} = flip withHeapStats fun =<< argsLogger++defaultMain :: Recorder (WithPriority Log) -> Arguments -> IO ()+defaultMain recorder Arguments{..} = withHeapStats (cmapWithPrio LogHeapStats recorder) fun  where+  log :: Priority -> Log -> IO ()+  log = logWith recorder+   fun = do     setLocaleEncoding utf8     pid <- T.pack . show <$> getProcessID     logger <- argsLogger     hSetBuffering stderr LineBuffering -    let hlsPlugin = asGhcIdePlugin argsHlsPlugins+    let hlsPlugin = asGhcIdePlugin (cmapWithPrio LogPluginHLS recorder) argsHlsPlugins         hlsCommands = allLspCmdIds' pid argsHlsPlugins         plugins = hlsPlugin <> argsGhcidePlugin         options = argsLspOptions { LSP.executeCommandCommands = LSP.executeCommandCommands argsLspOptions <> Just hlsCommands }@@ -274,29 +322,29 @@             LT.putStrLn $ decodeUtf8 $ A.encodePretty $ pluginsToDefaultConfig argsHlsPlugins         LSP -> withNumCapabilities (maybe (numProcessors `div` 2) fromIntegral argsThreads) $ do             t <- offsetTime-            logInfo logger "Starting LSP server..."-            logInfo logger "If you are seeing this in a terminal, you probably should have run WITHOUT the --lsp option!"-            runLanguageServer options inH outH argsGetHieDbLoc argsDefaultHlsConfig argsOnConfigChange (pluginHandlers plugins) $ \env vfs rootPath withHieDb hieChan -> do+            log Info LogLspStart++            runLanguageServer (cmapWithPrio LogLanguageServer recorder) options inH outH argsGetHieDbLoc argsDefaultHlsConfig argsOnConfigChange (pluginHandlers plugins) $ \env rootPath withHieDb hieChan -> do                 traverse_ IO.setCurrentDirectory rootPath                 t <- t-                logInfo logger $ T.pack $ "Started LSP server in " ++ showDuration t+                log Info $ LogLspStartDuration t                  dir <- maybe IO.getCurrentDirectory return rootPath                  -- We want to set the global DynFlags right now, so that we can use                 -- `unsafeGlobalDynFlags` even before the project is configured                 _mlibdir <--                    setInitialDynFlags logger dir argsSessionLoadingOptions-                        `catchAny` (\e -> (logDebug logger $ T.pack $ "setInitialDynFlags: " ++ displayException e) >> pure Nothing)-+                    setInitialDynFlags (cmapWithPrio LogSession recorder) dir argsSessionLoadingOptions+                        -- TODO: should probably catch/log/rethrow at top level instead+                        `catchAny` (\e -> log Error (LogSetInitialDynFlagsException e) >> pure Nothing) -                sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions dir+                sessionLoader <- loadSessionWithOptions (cmapWithPrio LogSession recorder) argsSessionLoadingOptions dir                 config <- LSP.runLspT env LSP.getConfig                 let def_options = argsIdeOptions config sessionLoader                  -- disable runSubset if the client doesn't support watched files                 runSubset <- (optRunSubset def_options &&) <$> LSP.runLspT env isWatchSupported-                logDebug logger $ T.pack $ "runSubset: " <> show runSubset+                log Debug $ LogShouldRunSubset runSubset                  let options = def_options                             { optReportProgress = clientSupportsProgress caps@@ -306,24 +354,22 @@                     caps = LSP.resClientCapabilities env                 -- FIXME: Remove this after GHC 9 gets fully supported                 when (ghcVersion == GHC90) $-                    hPutStrLn stderr $-                        "Currently, HLS supports GHC 9 only partially. "-                        <> "See [issue #297](https://github.com/haskell/haskell-language-server/issues/297) for more detail."+                    log Warning LogOnlyPartialGhc9Support                 initialise+                    (cmapWithPrio LogService recorder)                     argsDefaultHlsConfig                     rules                     (Just env)                     logger                     debouncer                     options-                    vfs                     withHieDb                     hieChan             dumpSTMStats         Check argFiles -> do           dir <- maybe IO.getCurrentDirectory return argsProjectRoot           dbLoc <- getHieDbLoc dir-          runWithDb logger dbLoc $ \hiedb hieChan -> do+          runWithDb (cmapWithPrio LogSession recorder) 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@@ -344,16 +390,15 @@             putStrLn $ "Found " ++ show n ++ " cradle" ++ ['s' | n /= 1]             when (n > 0) $ putStrLn $ "  (" ++ intercalate ", " (catMaybes ucradles) ++ ")"             putStrLn "\nStep 3/4: Initializing the IDE"-            vfs <- makeVFSHandle-            sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions dir+            sessionLoader <- loadSessionWithOptions (cmapWithPrio LogSession recorder) argsSessionLoadingOptions dir             let def_options = argsIdeOptions argsDefaultHlsConfig sessionLoader                 options = def_options                         { optCheckParents = pure NeverCheck                         , optCheckProject = pure False                         , optModifyDynFlags = optModifyDynFlags def_options <> pluginModifyDynflags plugins                         }-            ide <- initialise argsDefaultHlsConfig rules Nothing logger debouncer options vfs hiedb hieChan-            shakeSessionInit ide+            ide <- initialise (cmapWithPrio LogService recorder) argsDefaultHlsConfig rules Nothing logger debouncer options hiedb hieChan+            shakeSessionInit (cmapWithPrio LogShake recorder) ide             registerIdeConfiguration (shakeExtras ide) $ IdeConfiguration mempty (hashed Nothing)              putStrLn "\nStep 4/4: Type checking the files"@@ -388,26 +433,25 @@             root <-  maybe IO.getCurrentDirectory return argsProjectRoot             dbLoc <- getHieDbLoc root             hPutStrLn stderr $ "Using hiedb at: " ++ dbLoc-            mlibdir <- setInitialDynFlags logger root def+            mlibdir <- setInitialDynFlags (cmapWithPrio LogSession recorder) root def             rng <- newStdGen             case mlibdir of                 Nothing     -> exitWith $ ExitFailure 1-                Just libdir -> retryOnSqliteBusy logger rng (HieDb.runCommand libdir opts{HieDb.database = dbLoc} cmd)+                Just libdir -> retryOnSqliteBusy (cmapWithPrio LogSession recorder) rng (HieDb.runCommand libdir opts{HieDb.database = dbLoc} cmd)          Custom (IdeCommand c) -> do           root <-  maybe IO.getCurrentDirectory return argsProjectRoot           dbLoc <- getHieDbLoc root-          runWithDb logger dbLoc $ \hiedb hieChan -> do-            vfs <- makeVFSHandle-            sessionLoader <- loadSessionWithOptions argsSessionLoadingOptions "."+          runWithDb (cmapWithPrio LogSession recorder) dbLoc $ \hiedb hieChan -> do+            sessionLoader <- loadSessionWithOptions (cmapWithPrio LogSession recorder) argsSessionLoadingOptions "."             let def_options = argsIdeOptions argsDefaultHlsConfig sessionLoader                 options = def_options                     { optCheckParents = pure NeverCheck                     , optCheckProject = pure False                     , optModifyDynFlags = optModifyDynFlags def_options <> pluginModifyDynflags plugins                     }-            ide <- initialise argsDefaultHlsConfig rules Nothing logger debouncer options vfs hiedb hieChan-            shakeSessionInit ide+            ide <- initialise (cmapWithPrio LogService recorder) argsDefaultHlsConfig rules Nothing logger debouncer options hiedb hieChan+            shakeSessionInit (cmapWithPrio LogShake recorder) ide             registerIdeConfiguration (shakeExtras ide) $ IdeConfiguration mempty (hashed Nothing)             c ide 
src/Development/IDE/Main/HeapStats.hs view
@@ -1,53 +1,75 @@-{-# LANGUAGE NumericUnderscores #-}
--- | Logging utilities for reporting heap statistics
-module Development.IDE.Main.HeapStats ( withHeapStats ) where
-
-import           Control.Concurrent
-import           Control.Concurrent.Async
-import           Control.Monad
-import qualified Data.Text                    as T
-import           Data.Word
-import           Development.IDE.Types.Logger (Logger, logInfo)
-import           GHC.Stats
-import           Text.Printf                  (printf)
-
--- | Interval at which to report the latest heap statistics.
-heapStatsInterval :: Int
-heapStatsInterval = 60_000_000 -- 60s
-
--- | Report the live bytes and heap size at the last major collection.
-logHeapStats :: Logger -> IO ()
-logHeapStats l = do
-  stats <- getRTSStats
-  -- live_bytes is the total amount of live memory in a program
-  -- (corresponding to the amount on a heap profile)
-  let live_bytes = gcdetails_live_bytes (gc stats)
-  -- heap_size is the total amount of memory the RTS is using
-  -- this corresponds closer to OS memory usage
-      heap_size  = gcdetails_mem_in_use_bytes (gc stats)
-      format :: Word64 -> T.Text
-      format m = T.pack (printf "%.2fMB" (fromIntegral @Word64 @Double m / 1e6))
-      message = "Live bytes: " <> format live_bytes  <> " " <>
-                "Heap size: " <> format heap_size
-  logInfo l message
-
--- | An action which logs heap statistics at the 'heapStatsInterval'
-heapStatsThread :: Logger -> IO r
-heapStatsThread l = forever $ do
-  threadDelay heapStatsInterval
-  logHeapStats l
-
--- | A helper function which lauches the 'heapStatsThread' and kills it
--- appropiately when the inner action finishes. It also checks to see
--- if `-T` is enabled.
-withHeapStats :: Logger -> IO r -> IO r
-withHeapStats l k = do
-  enabled <- getRTSStatsEnabled
-  if enabled
-    then do
-      logInfo l ("Logging heap statistics every "
-                  <> T.pack (printf "%.2fs" (fromIntegral @Int @Double heapStatsInterval / 1e6)))
-      withAsync (heapStatsThread l) (const k)
-    else do
-      logInfo l "Heap statistics are not enabled (RTS option -T is needed)"
-      k
+{-# LANGUAGE NumericUnderscores #-}+-- | Logging utilities for reporting heap statistics+module Development.IDE.Main.HeapStats ( withHeapStats, Log(..)) where++import           Control.Concurrent+import           Control.Concurrent.Async+import           Control.Monad+import           Data.Word+import           Development.IDE.Types.Logger (Pretty (pretty), Priority (Info),+                                               Recorder, WithPriority, hsep,+                                               logWith, (<+>))+import           GHC.Stats+import           Text.Printf                  (printf)++data Log+  = LogHeapStatsPeriod !Int+  | LogHeapStatsDisabled+  | LogHeapStats !Word64 !Word64+  deriving Show++instance Pretty Log where+  pretty log = case log of+    LogHeapStatsPeriod period ->+      "Logging heap statistics every" <+> pretty (toFormattedSeconds period)+    LogHeapStatsDisabled ->+      "Heap statistics are not enabled (RTS option -T is needed)"+    LogHeapStats liveBytes heapSize ->+      hsep+        [ "Live bytes:"+        , pretty (toFormattedMegabytes liveBytes)+        , "Heap size:"+        , pretty (toFormattedMegabytes heapSize) ]+    where+      toFormattedSeconds :: Int -> String+      toFormattedSeconds s = printf "%.2fs" (fromIntegral @Int @Double s / 1e6)++      toFormattedMegabytes :: Word64 -> String+      toFormattedMegabytes b = printf "%.2fMB" (fromIntegral @Word64 @Double b / 1e6)++-- | Interval at which to report the latest heap statistics.+heapStatsInterval :: Int+heapStatsInterval = 60_000_000 -- 60s++-- | Report the live bytes and heap size at the last major collection.+logHeapStats :: Recorder (WithPriority Log) -> IO ()+logHeapStats l = do+  stats <- getRTSStats+  -- live_bytes is the total amount of live memory in a program+  -- (corresponding to the amount on a heap profile)+  let live_bytes = gcdetails_live_bytes (gc stats)+  -- heap_size is the total amount of memory the RTS is using+  -- this corresponds closer to OS memory usage+      heap_size  = gcdetails_mem_in_use_bytes (gc stats)+  logWith l Info $ LogHeapStats live_bytes heap_size++-- | An action which logs heap statistics at the 'heapStatsInterval'+heapStatsThread :: Recorder (WithPriority Log) -> IO r+heapStatsThread l = forever $ do+  threadDelay heapStatsInterval+  logHeapStats l++-- | A helper function which lauches the 'heapStatsThread' and kills it+-- appropiately when the inner action finishes. It also checks to see+-- if `-T` is enabled.+withHeapStats :: Recorder (WithPriority Log) -> IO r -> IO r+withHeapStats l k = do+  enabled <- getRTSStatsEnabled+  if enabled+    then do+      logWith l Info $ LogHeapStatsPeriod heapStatsInterval+      withAsync (heapStatsThread l) (const k)+    else do+      logWith l Info LogHeapStatsDisabled+      k+
src/Development/IDE/Plugin/CodeAction.hs view
@@ -48,15 +48,13 @@ import           Development.IDE.GHC.Compat.Util import           Development.IDE.GHC.Error import           Development.IDE.GHC.ExactPrint-import           Development.IDE.GHC.Util                          (prettyPrint,+import           Development.IDE.GHC.Util                          (printOutputable,                                                                     printRdrName,-                                                                    traceAst,-                                                                    unsafePrintSDoc)+                                                                    traceAst) import           Development.IDE.Plugin.CodeAction.Args import           Development.IDE.Plugin.CodeAction.ExactPrint import           Development.IDE.Plugin.CodeAction.PositionIndexed import           Development.IDE.Plugin.TypeLenses                 (suggestSignature)-import           Development.IDE.Spans.Common import           Development.IDE.Types.Exports import           Development.IDE.Types.Location import           Development.IDE.Types.Options@@ -546,7 +544,7 @@       isTheBinding span = srcSpanToRange span == Just _range        isSameName :: IdP GhcPs -> String -> Bool-      isSameName x name = showSDocUnsafe (ppr x) == name+      isSameName x name = T.unpack (printOutputable x) == name  data ExportsAs = ExportName | ExportPattern | ExportFamily | ExportAll   deriving (Eq)@@ -711,7 +709,7 @@       , _start `isInsideSrcSpan` l]     , nextLineP <- Position{ _line = _line lastLineP + 1, _character = 0}     = [ ("Define " <> sig-        , [TextEdit (Range nextLineP nextLineP) (T.unlines ["", sig, name <> " = error \"not implemented\""])]+        , [TextEdit (Range nextLineP nextLineP) (T.unlines ["", sig, name <> " = _"])]         )]     | otherwise = []   where@@ -719,7 +717,6 @@     sig = name <> colon <> T.dropWhileEnd isSpace typ     ParsedModule{pm_parsed_source = L _ HsModule{hsmodDecls}} = parsedModule - suggestFillTypeWildcard :: Diagnostic -> [(T.Text, TextEdit)] suggestFillTypeWildcard Diagnostic{_range=_range,..} -- Foo.hs:3:8: error:@@ -1014,7 +1011,7 @@ occursUnqualified _ _ = False  symbolOccursIn :: T.Text -> IE GhcPs -> Bool-symbolOccursIn symb = any ((== symb). showNameWithoutUniques) . ieNames+symbolOccursIn symb = any ((== symb). printOutputable) . ieNames  targetModuleName :: ModuleTarget -> ModuleName targetModuleName ImplicitPrelude{} = mkModuleName "Prelude"@@ -1048,12 +1045,12 @@          in Right <$> [ if parensed                 then Rewrite (rangeToSrcSpan "<dummy>" _range) $ \df ->                     liftParseAST @(HsExpr GhcPs) df $-                    prettyPrint $+                    T.unpack $ printOutputable $                         HsVar @GhcPs noExtField $                             reLocA $ L (mkGeneralSrcSpan  "") rdr                 else Rewrite (rangeToSrcSpan "<dummy>" _range) $ \df ->                     liftParseAST @RdrName df $-                    prettyPrint $ L (mkGeneralSrcSpan  "") rdr+                    T.unpack $ printOutputable $ L (mkGeneralSrcSpan  "") rdr             ] findImportDeclByRange :: [LImportDecl GhcPs] -> Range -> Maybe (LImportDecl GhcPs) findImportDeclByRange xs range = find (\(L (locA -> l) _)-> srcSpanToRange l == Just range) xs@@ -1424,7 +1421,7 @@      symImp             | Just symbol <- mSymbol               , symOcc <- mkVarOcc $ T.unpack symbol =-              " (" <> T.pack (unsafePrintSDoc (parenSymOcc symOcc $ ppr symOcc)) <> ")"+              " (" <> printOutputable (parenSymOcc symOcc $ ppr symOcc) <> ")"             | otherwise = ""      impStmt =        "import "@@ -1530,14 +1527,22 @@       curr <- textInRange range <$> contents       pure $ "`" `T.isPrefixOf` curr && "`" `T.isSuffixOf` curr ++-- | Extract the type and surround it in parentheses except in obviously safe cases.+--+-- Inferring when parentheses are actually needed around the type signature would+-- require understanding both the precedence of the context of the hole and of+-- the signature itself. Inserting them (almost) unconditionally is ugly but safe. extractWildCardTypeSignature :: T.Text -> T.Text-extractWildCardTypeSignature =-  -- inferring when parens are actually needed around the type signature would-  -- require understanding both the precedence of the context of the _ and of-  -- the signature itself. Inserting them unconditionally is ugly but safe.-  ("(" `T.append`) . (`T.append` ")") .-  T.takeWhile (/='’') . T.dropWhile (=='‘') . T.dropWhile (/='‘') .-  snd . T.breakOnEnd "standing for "+extractWildCardTypeSignature msg = (if enclosed || not application then id else bracket) signature+  where+    msgSigPart = snd $ T.breakOnEnd "standing for " msg+    signature = T.takeWhile (/='’') . T.dropWhile (=='‘') . T.dropWhile (/='‘') $ msgSigPart+    -- parenthesize type applications, e.g. (Maybe Char)+    application = any isSpace . T.unpack $ signature+    -- do not add extra parentheses to lists, tuples and already parenthesized types+    enclosed = not (T.null signature) && (T.head signature, T.last signature) `elem` [('(',')'), ('[',']')]+    bracket = ("(" `T.append`) . (`T.append` ")")  extractRenamableTerms :: T.Text -> [T.Text] extractRenamableTerms msg@@ -1610,32 +1615,32 @@     b' = wrapOperatorInParens . unqualify $ b #if !MIN_VERSION_ghc(9,2,0)     ranges' (L _ (IEThingWith _ thing _  inners labels))-      | showSDocUnsafe (ppr thing) == b' = []+      | T.unpack (printOutputable thing) == b' = []       | otherwise =-          [ locA l' | L l' x <- inners, showSDocUnsafe (ppr x) == b']-          ++ [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b']+          [ locA l' | L l' x <- inners, T.unpack (printOutputable x) == b']+          ++ [ l' | L l' x <- labels, T.unpack (printOutputable x) == b'] #else     ranges' (L _ (IEThingWith _ thing _  inners))-      | showSDocUnsafe (ppr thing) == b' = []+      | T.unpack (printOutputable thing) == b' = []       | otherwise =-          [ locA l' | L l' x <- inners, showSDocUnsafe (ppr x) == b']+          [ locA l' | L l' x <- inners, T.unpack (printOutputable x) == b'] #endif     ranges' _ = []  rangesForBinding' :: String -> LIE GhcPs -> [SrcSpan]-rangesForBinding' b (L (locA -> l) x@IEVar{}) | showSDocUnsafe (ppr x) == b = [l]-rangesForBinding' b (L (locA -> l) x@IEThingAbs{}) | showSDocUnsafe (ppr x) == b = [l]-rangesForBinding' b (L (locA -> l) (IEThingAll _ x)) | showSDocUnsafe (ppr x) == b = [l]+rangesForBinding' b (L (locA -> l) x@IEVar{}) | T.unpack (printOutputable x) == b = [l]+rangesForBinding' b (L (locA -> l) x@IEThingAbs{}) | T.unpack (printOutputable x) == b = [l]+rangesForBinding' b (L (locA -> l) (IEThingAll _ x)) | T.unpack (printOutputable x) == b = [l] #if !MIN_VERSION_ghc(9,2,0) rangesForBinding' b (L l (IEThingWith _ thing _  inners labels)) #else rangesForBinding' b (L (locA -> l) (IEThingWith _ thing _  inners)) #endif-    | showSDocUnsafe (ppr thing) == b = [l]+    | T.unpack (printOutputable thing) == b = [l]     | otherwise =-        [ locA l' | L l' x <- inners, showSDocUnsafe (ppr x) == b]+        [ locA l' | L l' x <- inners, T.unpack (printOutputable x) == b] #if !MIN_VERSION_ghc(9,2,0)-        ++ [ l' | L l' x <- labels, showSDocUnsafe (ppr x) == b]+        ++ [ l' | L l' x <- labels, T.unpack (printOutputable x) == b] #endif rangesForBinding' _ _ = [] @@ -1726,6 +1731,13 @@       --       -- @P@ and @?@ can be a data type and a constructor, a class and a method,       -- a class and an associated type/data family, etc.++    | ImportAllConstructors T.Text+      -- ^ Import all constructors for a specific data type.+      --+      -- import M (P(..))+      --+      -- @P@ can be a data type or a class.   deriving Show  importStyles :: IdentInfo -> NonEmpty ImportStyle@@ -1734,7 +1746,9 @@     -- Constructors always have to be imported via their parent data type, but     -- methods and associated type/data families can also be imported as     -- top-level exports.-  = ImportViaParent rendered p :| [ImportTopLevel rendered | not isDatacon]+  = ImportViaParent rendered p+      :| [ImportTopLevel rendered | not isDatacon]+      <> [ImportAllConstructors p]   | otherwise   = ImportTopLevel rendered :| [] @@ -1743,15 +1757,19 @@ renderImportStyle (ImportTopLevel x)   = x renderImportStyle (ImportViaParent x p@(T.uncons -> Just ('(', _))) = "type " <> p <> "(" <> x <> ")" renderImportStyle (ImportViaParent x p) = p <> "(" <> x <> ")"+renderImportStyle (ImportAllConstructors p) = p <> "(..)"  -- | Used for extending import lists unImportStyle :: ImportStyle -> (Maybe String, String) unImportStyle (ImportTopLevel x)    = (Nothing, T.unpack x) unImportStyle (ImportViaParent x y) = (Just $ T.unpack y, T.unpack x)+unImportStyle (ImportAllConstructors x) = (Just $ T.unpack x, wildCardSymbol) + quickFixImportKind' :: T.Text -> ImportStyle -> CodeActionKind quickFixImportKind' x (ImportTopLevel _) = CodeActionUnknown $ "quickfix.import." <> x <> ".list.topLevel" quickFixImportKind' x (ImportViaParent _ _) = CodeActionUnknown $ "quickfix.import." <> x <> ".list.withParent"+quickFixImportKind' x (ImportAllConstructors _) = CodeActionUnknown $ "quickfix.import." <> x <> ".list.allConstructors"  quickFixImportKind :: T.Text -> CodeActionKind quickFixImportKind x = CodeActionUnknown $ "quickfix.import." <> x
src/Development/IDE/Plugin/CodeAction/Args.hs view
@@ -65,7 +65,7 @@           pure $ localExports <> pkgExports         _ -> pure mempty   caaIdeOptions <- onceIO $ runAction "GhcideCodeActions.getIdeOptions" state getIdeOptions-  caaParsedModule <- onceIO $ runRule GetParsedModule+  caaParsedModule <- onceIO $ runRule GetParsedModuleWithComments   caaContents <-     onceIO $       runRule GetFileContents >>= \case
src/Development/IDE/Plugin/CodeAction/ExactPrint.hs view
@@ -19,6 +19,8 @@   extendImport,   hideSymbol,   liftParseAST,++  wildCardSymbol ) where  import           Control.Applicative@@ -33,7 +35,7 @@ import           Data.Maybe                            (fromJust, isNothing,                                                         mapMaybe) import qualified Data.Text                             as T-import           Development.IDE.GHC.Compat+import           Development.IDE.GHC.Compat hiding (Annotation) import           Development.IDE.GHC.Error import           Development.IDE.GHC.ExactPrint import           Development.IDE.Spans.Common@@ -49,7 +51,7 @@                       DeltaPos (SameLine), EpAnn (..), EpaLocation (EpaDelta),                       IsUnicodeSyntax (NormalSyntax),                       NameAdornment (NameParens), NameAnn (..), addAnns, ann, emptyComments,-                      reAnnL, AnnList (..))+                      reAnnL, AnnList (..), TrailingAnn (AddCommaAnn), addTrailingAnnToA) #endif import           Language.LSP.Types import Development.IDE.GHC.Util@@ -330,6 +332,7 @@ extendImport mparent identifier lDecl@(L l _) =   Rewrite (locA l) $ \df -> do     case mparent of+      -- This will also work for `ImportAllConstructors`       Just parent -> extendImportViaParent df parent identifier lDecl       _           -> extendImportTopLevel identifier lDecl @@ -352,8 +355,8 @@     top <- uniqueSrcSpanT     let rdr = reLocA $ L src $ mkRdrUnqual $ mkVarOcc thing     let alreadyImported =-          showNameWithoutUniques (occName (unLoc rdr))-            `elem` map (showNameWithoutUniques @OccName) (listify (const True) lies)+          printOutputable (occName (unLoc rdr))+            `elem` map (printOutputable @OccName) (listify (const True) lies)     when alreadyImported $       lift (Left $ thing <> " already imported") @@ -374,15 +377,14 @@           transferAnn (L l' lies) (L l' [x]) id         return $ L l it{ideclHiding = Just (hide, L l' $ lies ++ [x])} #else--        x <- pure $ setEntryDP x (SameLine $ if hasSibling then 1 else 0)--        let fixLast = if hasSibling then first addComma else id-            lies' = over _last fixLast lies ++ [x]+        lies' <- addCommaInImportList lies x         return $ L l it{ideclHiding = Just (hide, L l' lies')} #endif extendImportTopLevel _ _ = lift $ Left "Unable to extend the import list" +wildCardSymbol :: String+wildCardSymbol = ".."+ -- | Add an identifier with its parent to import list -- -- extendImportViaParent "Bar" "Cons" AST:@@ -393,6 +395,11 @@ -- import A () --> import A (Bar(Cons)) -- import A (Foo, Bar) --> import A (Foo, Bar(Cons)) -- import A (Foo, Bar()) --> import A (Foo, Bar(Cons))+--+-- extendImportViaParent "Bar" ".." AST:+-- import A () --> import A (Bar(..))+-- import A (Foo, Bar) -> import A (Foo, Bar(..))+-- import A (Foo, Bar()) -> import A (Foo, Bar(..)) extendImportViaParent ::   DynFlags ->   -- | parent (already parenthesized if needs)@@ -428,6 +435,19 @@ #endif     -- ThingWith ie lies' => ThingWith ie (lies' ++ [child])     | parent == unIEWrappedName ie+    , child == wildCardSymbol = do+#if MIN_VERSION_ghc(9,2,0)+        let it' = it{ideclHiding = Just (hide, lies)}+            thing = IEThingWith newl twIE (IEWildcard 2) []+            newl = (\ann -> ann ++ [(AddEpAnn AnnDotdot d0)]) <$> l'''+            lies = L l' $ reverse pre ++ [L l'' thing] ++ xs+        return $ L l it'+#else+        let thing = L l'' (IEThingWith noExtField twIE (IEWildcard 2)  [] [])+        modifyAnnsT (Map.map (\ann -> ann{annsDP = (G AnnDotdot, dp00) : annsDP ann}))+        return $ L l it{ideclHiding = Just (hide, L l' $ reverse pre ++ [thing] ++ xs)}+#endif+    | parent == unIEWrappedName ie     , hasSibling <- not $ null lies' =       do         srcChild <- uniqueSrcSpanT@@ -436,8 +456,8 @@         childRdr <- pure $ setEntryDP childRdr $ SameLine $ if hasSibling then 1 else 0 #endif         let alreadyImported =-              showNameWithoutUniques (occName (unLoc childRdr))-                `elem` map (showNameWithoutUniques @OccName) (listify (const True) lies')+              printOutputable (occName (unLoc childRdr))+                `elem` map (printOutputable @OccName) (listify (const True) lies')         when alreadyImported $           lift (Left $ child <> " already included in " <> parent <> " imports") @@ -452,9 +472,7 @@             lies = L l' $ reverse pre ++                 [L l'' (IEThingWith l''' twIE NoIEWildcard (over _last fixLast lies' ++ [childLIE]))] ++ xs             fixLast = if hasSibling then first addComma else id-        return $ if hasSibling-            then L l it'-            else L l it'+        return $ L l it' #endif   go hide l' pre (x : xs) = go hide l' (x : pre) xs   go hide l' pre []@@ -490,15 +508,41 @@       -- we need change the ann key from `[]` to `:` to keep parens and other anns.       unless hasSibling $         transferAnn (L l' $ reverse pre) (L l' [x]) id++      let lies' = reverse pre ++ [x] #else-          x :: LIE GhcPs = reLocA $ L l'' $ IEThingWith listAnn parentLIE NoIEWildcard [childLIE]           listAnn = epAnn srcParent [AddEpAnn AnnOpenP (epl 1), AddEpAnn AnnCloseP (epl 0)]+          x :: LIE GhcPs = reLocA $ L l'' $ IEThingWith listAnn parentLIE NoIEWildcard [childLIE]++      let hasSibling = not (null pre)+      lies' <- addCommaInImportList (reverse pre) x #endif-      return $ L l it{ideclHiding = Just (hide, L l' $ reverse pre ++ [x])}+      return $ L l it{ideclHiding = Just (hide, L l' lies')} extendImportViaParent _ _ _ _ = lift $ Left "Unable to extend the import list via parent" +#if MIN_VERSION_ghc(9,2,0)+-- Add an item in an import list, taking care of adding comma if needed.+addCommaInImportList :: Monad m =>+  -- | Initial list+  [LocatedAn AnnListItem a]+  -- | Additionnal item+  -> LocatedAn AnnListItem a+  -> m [LocatedAn AnnListItem a]+addCommaInImportList lies x = do+  let hasSibling = not (null lies)+  -- Add the space before the comma+  x <- pure $ setEntryDP x (SameLine $ if hasSibling then 1 else 0)++  -- Add the comma (if needed)+  let+    fixLast = if hasSibling then first addComma else id+    lies' = over _last fixLast lies ++ [x]++  pure lies'+#endif+ unIEWrappedName :: IEWrappedName (IdP GhcPs) -> String-unIEWrappedName (occName -> occ) = showSDocUnsafe $ parenSymOcc occ (ppr occ)+unIEWrappedName (occName -> occ) = T.unpack $ printOutputable $ parenSymOcc occ (ppr occ)  hasParen :: String -> Bool hasParen ('(' : _) = True
src/Development/IDE/Plugin/Completions.hs view
@@ -4,6 +4,7 @@  module Development.IDE.Plugin.Completions     ( descriptor+    , Log(..)     ) where  import           Control.Concurrent.Async                     (concurrently)@@ -19,12 +20,14 @@ import qualified Data.Text                                    as T import           Development.IDE.Core.PositionMapping import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Service-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Service                 hiding (Log,+                                                               LogShake)+import           Development.IDE.Core.Shake                   hiding (Log)+import qualified Development.IDE.Core.Shake                   as Shake import           Development.IDE.GHC.Compat import           Development.IDE.GHC.Error                    (rangeToSrcSpan) import           Development.IDE.GHC.ExactPrint               (GetAnnotatedParsedSource (GetAnnotatedParsedSource))-import           Development.IDE.GHC.Util                     (prettyPrint)+import           Development.IDE.GHC.Util                     (printOutputable) import           Development.IDE.Graph import           Development.IDE.Plugin.CodeAction            (newImport,                                                                newImportToEdit)@@ -36,6 +39,10 @@                                                                hscEnv) import qualified Development.IDE.Types.KnownTargets           as KT import           Development.IDE.Types.Location+import           Development.IDE.Types.Logger                 (Pretty (pretty),+                                                               Recorder,+                                                               WithPriority,+                                                               cmapWithPrio) import           GHC.Exts                                     (fromList, toList) import           Ide.Plugin.Config                            (Config) import           Ide.Types@@ -44,17 +51,23 @@ import qualified Language.LSP.VFS                             as VFS import           Text.Fuzzy.Parallel                          (Scored (..)) -descriptor :: PluginId -> PluginDescriptor IdeState-descriptor plId = (defaultPluginDescriptor plId)-  { pluginRules = produceCompletions+data Log = LogShake Shake.Log deriving Show++instance Pretty Log where+  pretty = \case+    LogShake log -> pretty log++descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState+descriptor recorder plId = (defaultPluginDescriptor plId)+  { pluginRules = produceCompletions recorder   , pluginHandlers = mkPluginHandler STextDocumentCompletion getCompletionsLSP   , pluginCommands = [extendImportCommand]   , pluginConfigDescriptor = defaultConfigDescriptor {configCustomConfig = mkCustomConfig properties}   } -produceCompletions :: Rules ()-produceCompletions = do-    define $ \LocalCompletions file -> do+produceCompletions :: Recorder (WithPriority Log) -> Rules ()+produceCompletions recorder = do+    define (cmapWithPrio LogShake recorder) $ \LocalCompletions file -> do         let uri = fromNormalizedUri $ normalizedFilePathToUri file         pm <- useWithStale GetParsedModule file         case pm of@@ -62,7 +75,7 @@                 let cdata = localCompletionsForParsedModule uri pm                 return ([], Just cdata)             _ -> return ([], Nothing)-    define $ \NonLocalCompletions file -> do+    define (cmapWithPrio LogShake recorder) $ \NonLocalCompletions file -> do         -- For non local completions we avoid depending on the parsed module,         -- synthetizing a fake module with an empty body from the buffer         -- in the ModSummary, which preserves all the imports@@ -200,7 +213,7 @@           <> "’ from "           <> importName           <> " (at "-          <> T.pack (prettyPrint srcSpan)+          <> printOutputable srcSpan           <> ")"     void $ LSP.sendRequest SWorkspaceApplyEdit (ApplyWorkspaceEditParams Nothing wedit) (\_ -> pure ())   return $ Right Null
src/Development/IDE/Plugin/Completions/Logic.hs view
@@ -31,7 +31,7 @@ import           Data.Functor import qualified Data.HashMap.Strict                      as HM import qualified Data.HashSet                             as HashSet-import           Data.Monoid                              (First(..))+import           Data.Monoid                              (First (..)) import           Data.Ord                                 (Down (Down)) import qualified Data.Set                                 as Set import           Development.IDE.Core.Compile@@ -63,7 +63,7 @@ import           Language.LSP.Types import           Language.LSP.Types.Capabilities import qualified Language.LSP.VFS                         as VFS-import           Text.Fuzzy.Parallel                      (Scored (score_),+import           Text.Fuzzy.Parallel                      (Scored (score),                                                            original)  -- Chunk size used for parallelizing fuzzy matching@@ -204,7 +204,7 @@   where kind = Just compKind         docs' = imported : spanDocToMarkdown docs         imported = case provenance of-          Local pos  -> "*Defined at " <> pprLineCol (srcSpanStart pos) <> " in this module*\n'"+          Local pos  -> "*Defined at " <> pprLineCol (srcSpanStart pos) <> " in this module*\n"           ImportedFrom mod -> "*Imported from '" <> mod <> "'*\n"           DefinedIn mod -> "*Defined in '" <> mod <> "'*\n"         colon = if optNewColonConvention then ": " else ":: "@@ -214,7 +214,7 @@         pprLineCol :: SrcLoc -> T.Text         pprLineCol (UnhelpfulLoc fs) = T.pack $ unpackFS fs         pprLineCol (RealSrcLoc loc _) =-            "line " <> ppr(srcLocLine loc) <> ", column " <> ppr(srcLocCol loc)+            "line " <> printOutputable (srcLocLine loc) <> ", column " <> printOutputable (srcLocCol loc)   mkAdditionalEditsCommand :: PluginId -> ExtendImport -> Command@@ -226,16 +226,16 @@   where     compKind = occNameToComKind typeText origName     isTypeCompl = isTcOcc origName-    label = stripPrefix $ showGhc origName+    label = stripPrefix $ printOutputable origName     insertText = case isInfix of             Nothing -> case getArgText <$> thingType of                             Nothing      -> label-                            Just argText -> label <> " " <> argText+                            Just argText -> if T.null argText then label else label <> " " <> argText             Just LeftSide -> label <> "`"              Just Surrounded -> label     typeText-          | Just t <- thingType = Just . stripForall $ showGhc t+          | Just t <- thingType = Just . stripForall $ printOutputable t           | otherwise = Nothing     additionalTextEdits =       imp <&> \x ->@@ -244,7 +244,7 @@             thingParent,             importName = showModName $ unLoc $ ideclName $ unLoc x,             importQual = getImportQual x,-            newThing = showNameWithoutUniques origName+            newThing = printOutputable origName           }      stripForall :: T.Text -> T.Text@@ -295,7 +295,7 @@     where         ctxt = defaultSDocContext{sdocStyle = mkUserStyle neverQualify AllTheWay} #else-showForSnippet x = showGhc x+showForSnippet x = printOutputable x #endif  mkModCompl :: T.Text -> CompletionItem@@ -350,7 +350,7 @@   let       packageState = hscEnv env       curModName = moduleName curMod-      curModNameText = ppr curModName+      curModNameText = printOutputable curModName        importMap = Map.fromList [ (l, imp) | imp@(L (locA -> (RealSrcSpan l _)) _) <- limports ] @@ -384,9 +384,9 @@                 -- we don't want to extend import if it's already in scope                 guard . null $ lookupGRE_Name inScopeEnv n                 -- or if it doesn't have a real location-                loc <- realSpan $ is_dloc spec+                loc <- realSpan $ is_dloc spec                 Map.lookup loc importMap-          compItem <- toCompItem par curMod (ppr $ is_mod spec) n originalImportDecl+          compItem <- toCompItem par curMod (printOutputable $ is_mod spec) n originalImportDecl           let unqual                 | is_qual spec = []                 | otherwise = compItem@@ -498,12 +498,12 @@ findRecordCompl :: Uri -> ParsedModule -> Provenance -> TyClDecl GhcPs -> [CompItem] findRecordCompl uri pmod mn DataDecl {tcdLName, tcdDataDefn} = result     where-        result = [mkRecordSnippetCompItem uri (Just $ showNameWithoutUniques $ unLoc tcdLName)-                        (showGhc . unLoc $ con_name) field_labels mn doc Nothing+        result = [mkRecordSnippetCompItem uri (Just $ printOutputable $ unLoc tcdLName)+                        (printOutputable . unLoc $ con_name) field_labels mn doc Nothing                  | ConDeclH98{..} <- unLoc <$> dd_cons tcdDataDefn                  , Just  con_details <- [getFlds con_args]                  , let field_names = concatMap extract con_details-                 , let field_labels = showGhc <$> field_names+                 , let field_labels = printOutputable <$> field_names                  , (not . List.null) field_labels                  ]         doc = SpanDocText (getDocumentation [pmod] $ reLoc tcdLName) (SpanDocUris Nothing Nothing)@@ -528,9 +528,6 @@         extract _ = [] findRecordCompl _ _ _ _ = [] -ppr :: Outputable a => a -> T.Text-ppr = T.pack . prettyPrint- toggleSnippets :: ClientCapabilities -> CompletionsConfig -> CompletionItem -> CompletionItem toggleSnippets ClientCapabilities {_textDocument} CompletionsConfig{..} =   removeSnippetsWhen (not $ enableSnippets && supported)@@ -590,7 +587,7 @@           $ (if T.null enteredQual then id else mapMaybe (T.stripPrefix enteredQual))             allModNamesAsNS -      filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls "" "" (label . snd)+      filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)         where            mcc = case maybe_parsed of@@ -668,7 +665,7 @@         return $           (fmap.fmap) snd $           sortBy (compare `on` lexicographicOrdering) $-          mergeListsBy (flip compare `on` score_)+          mergeListsBy (flip compare `on` score)             [ (fmap.fmap) (notQual,) filtModNameCompls             , (fmap.fmap) (notQual,) filtKeywordCompls             , (fmap.fmap.fmap) (toggleSnippets caps config) compls@@ -681,11 +678,11 @@         --  3. In-scope completions rank next         --  4. label alphabetical ordering next         --  4. detail alphabetical ordering (proxy for module)-        lexicographicOrdering Fuzzy.Scored{score_, original} =+        lexicographicOrdering Fuzzy.Scored{score, original} =           case original of             (isQual, CompletionItem{_label,_detail}) -> do               let isLocal = maybe False (":" `T.isPrefixOf`) _detail-              (Down isQual, Down score_, Down isLocal, _label, _detail)+              (Down isQual, Down score, Down isLocal, _label, _detail)   @@ -807,7 +804,7 @@ safeTyThingForRecord :: TyThing -> Maybe (T.Text, [T.Text]) safeTyThingForRecord (AnId _) = Nothing safeTyThingForRecord (AConLike dc) =-    let ctxStr =   showGhc . occName . conLikeName $ dc+    let ctxStr = printOutputable . occName . conLikeName $ dc         field_names = T.pack . unpackFS . flLabel <$> conLikeFieldLabels dc     in         Just (ctxStr, field_names)
src/Development/IDE/Plugin/HLS.hs view
@@ -6,11 +6,11 @@ module Development.IDE.Plugin.HLS     (       asGhcIdePlugin+    , Log(..)     ) where  import           Control.Exception            (SomeException) import           Control.Monad-import           Control.Monad.IO.Class import qualified Data.Aeson                   as J import           Data.Bifunctor import           Data.Dependent.Map           (DMap)@@ -22,7 +22,7 @@ import qualified Data.Map                     as Map import           Data.String import qualified Data.Text                    as T-import           Development.IDE.Core.Shake+import           Development.IDE.Core.Shake   hiding (Log) import           Development.IDE.Core.Tracing import           Development.IDE.Graph        (Rules) import           Development.IDE.LSP.Server@@ -33,6 +33,7 @@ import           Ide.PluginUtils              (getClientConfig) import           Ide.Types                    as HLS import qualified Language.LSP.Server          as LSP+import           Language.LSP.VFS import           Language.LSP.Types import qualified Language.LSP.Types           as J import           Text.Regex.TDFA.Text         ()@@ -43,13 +44,22 @@ -- --------------------------------------------------------------------- -- +data Log+  = LogNoEnabledPlugins+  deriving Show++instance Pretty Log where+  pretty = \case+    LogNoEnabledPlugins ->+      "extensibleNotificationPlugins no enabled plugins"+ -- | Map a set of plugins to the underlying ghcide engine.-asGhcIdePlugin :: IdePlugins IdeState -> Plugin Config-asGhcIdePlugin (IdePlugins ls) =+asGhcIdePlugin :: Recorder (WithPriority Log) -> IdePlugins IdeState -> Plugin Config+asGhcIdePlugin recorder (IdePlugins ls) =     mkPlugin rulesPlugins HLS.pluginRules <>     mkPlugin executeCommandPlugins HLS.pluginCommands <>     mkPlugin extensiblePlugins HLS.pluginHandlers <>-    mkPlugin extensibleNotificationPlugins HLS.pluginNotificationHandlers <>+    mkPlugin (extensibleNotificationPlugins recorder) HLS.pluginNotificationHandlers <>     mkPlugin dynFlagsPlugins HLS.pluginModifyDynflags     where @@ -171,8 +181,8 @@                 pure $ Right $ combineResponses m config caps params xs -- --------------------------------------------------------------------- -extensibleNotificationPlugins :: [(PluginId, PluginNotificationHandlers IdeState)] -> Plugin Config-extensibleNotificationPlugins xs = mempty { P.pluginHandlers = handlers }+extensibleNotificationPlugins :: Recorder (WithPriority Log) -> [(PluginId, PluginNotificationHandlers IdeState)] -> Plugin Config+extensibleNotificationPlugins recorder xs = mempty { P.pluginHandlers = handlers }   where     IdeNotificationHandlers handlers' = foldMap bakePluginId xs     bakePluginId :: (PluginId, PluginNotificationHandlers IdeState) -> IdeNotificationHandlers@@ -181,17 +191,17 @@       hs     handlers = mconcat $ do       (IdeNotification m :=> IdeNotificationHandler fs') <- DMap.assocs handlers'-      pure $ notificationHandler m $ \ide params -> do+      pure $ notificationHandler m $ \ide vfs params -> do         config <- Ide.PluginUtils.getClientConfig         let fs = filter (\(pid,_) -> plcGlobalOn $ configForPlugin config pid) fs'         case nonEmpty fs of           Nothing -> do-              liftIO $ logInfo (ideLogger ide) "extensibleNotificationPlugins no enabled plugins"+              logWith recorder Info LogNoEnabledPlugins               pure ()           Just fs -> do             -- We run the notifications in order, so the core ghcide provider             -- (which restarts the shake process) hopefully comes last-              mapM_ (\(pid,f) -> otTracedProvider pid (fromString $ show m) $ f ide params) fs+              mapM_ (\(pid,f) -> otTracedProvider pid (fromString $ show m) $ f ide vfs params) fs  -- --------------------------------------------------------------------- @@ -217,7 +227,7 @@  -- | Combine the 'PluginHandler' for all plugins newtype IdeNotificationHandler (m :: J.Method FromClient Notification)-  = IdeNotificationHandler [(PluginId, IdeState -> MessageParams m -> LSP.LspM Config ())]+  = IdeNotificationHandler [(PluginId, IdeState -> VFS -> MessageParams m -> LSP.LspM Config ())] -- type NotificationHandler (m :: Method FromClient Notification) = MessageParams m -> IO ()`  -- | Combine the 'PluginHandlers' for all plugins
src/Development/IDE/Plugin/HLS/GhcIde.hs view
@@ -5,6 +5,7 @@ module Development.IDE.Plugin.HLS.GhcIde   (     descriptors+  , Log(..)   ) where import           Control.Monad.IO.Class import           Development.IDE@@ -19,16 +20,28 @@ import           Language.LSP.Types import           Text.Regex.TDFA.Text                () -descriptors :: [PluginDescriptor IdeState]-descriptors =+data Log+  = LogNotifications Notifications.Log+  | LogCompletions Completions.Log+  | LogTypeLenses TypeLenses.Log+  deriving Show++instance Pretty Log where+  pretty = \case+    LogNotifications log -> pretty log+    LogCompletions log   -> pretty log+    LogTypeLenses log    -> pretty log++descriptors :: Recorder (WithPriority Log) -> [PluginDescriptor IdeState]+descriptors recorder =   [ descriptor "ghcide-hover-and-symbols",     CodeAction.iePluginDescriptor "ghcide-code-actions-imports-exports",     CodeAction.typeSigsPluginDescriptor "ghcide-code-actions-type-signatures",     CodeAction.bindingsPluginDescriptor "ghcide-code-actions-bindings",     CodeAction.fillHolePluginDescriptor "ghcide-code-actions-fill-holes",-    Completions.descriptor "ghcide-completions",-    TypeLenses.descriptor "ghcide-type-lenses",-    Notifications.descriptor "ghcide-core"+    Completions.descriptor (cmapWithPrio LogCompletions recorder) "ghcide-completions",+    TypeLenses.descriptor (cmapWithPrio LogTypeLenses recorder) "ghcide-type-lenses",+    Notifications.descriptor (cmapWithPrio LogNotifications recorder) "ghcide-core"   ]  -- ---------------------------------------------------------------------
src/Development/IDE/Plugin/Test.hs view
@@ -28,6 +28,7 @@ import           Development.IDE.Core.RuleTypes import           Development.IDE.Core.Service import           Development.IDE.Core.Shake+import           Development.IDE.Core.Rules import           Development.IDE.GHC.Compat import           Development.IDE.Graph                (Action) import qualified Development.IDE.Graph                as Graph@@ -64,6 +65,7 @@     | GarbageCollectDirtyKeys CheckParents Age    -- ^ :: [String] (list of keys collected)     | GetStoredKeys                  -- ^ :: [String] (list of keys in store)     | GetFilesOfInterest             -- ^ :: [FilePath]+    | GetRebuildsCount               -- ^ :: Int (number of times we recompiled with GHC)     deriving Generic     deriving anyclass (FromJSON, ToJSON) @@ -131,6 +133,9 @@ testRequestHandler s GetFilesOfInterest = do     ff <- liftIO $ getFilesOfInterest s     return $ Right $ toJSON $ map fromNormalizedFilePath $ HM.keys ff+testRequestHandler s GetRebuildsCount = do+    count <- liftIO $ runAction "get build count" s getRebuildCount+    return $ Right $ toJSON count  getDatabaseKeys :: (Graph.Result -> Step)     -> ShakeDatabase
src/Development/IDE/Plugin/TypeLenses.hs view
@@ -10,7 +10,8 @@   GlobalBindingTypeSig (..),   GetGlobalBindingTypeSigs (..),   GlobalBindingTypeSigsResult (..),-) where+  Log(..)+  ) where  import           Control.Concurrent.STM.Stats        (atomically) import           Control.DeepSeq                     (rwhnf)@@ -33,6 +34,7 @@ import           Development.IDE.Core.Rules          (IdeState, runAction) import           Development.IDE.Core.Service        (getDiagnostics) import           Development.IDE.Core.Shake          (getHiddenDiagnostics, use)+import qualified Development.IDE.Core.Shake          as Shake import           Development.IDE.GHC.Compat import           Development.IDE.GHC.Util            (printName) import           Development.IDE.Graph.Classes@@ -41,6 +43,9 @@                                                       Range (Range, _end, _start),                                                       toNormalizedFilePath',                                                       uriToFilePath')+import           Development.IDE.Types.Logger        (Pretty (pretty), Recorder,+                                                      WithPriority,+                                                      cmapWithPrio) import           GHC.Generics                        (Generic) import           Ide.Plugin.Config                   (Config) import           Ide.Plugin.Properties@@ -68,15 +73,21 @@                                                       WorkspaceEdit (WorkspaceEdit)) import           Text.Regex.TDFA                     ((=~), (=~~)) +data Log = LogShake Shake.Log deriving Show++instance Pretty Log where+  pretty = \case+    LogShake log -> pretty log+ typeLensCommandId :: T.Text typeLensCommandId = "typesignature.add" -descriptor :: PluginId -> PluginDescriptor IdeState-descriptor plId =+descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState+descriptor recorder plId =   (defaultPluginDescriptor plId)     { pluginHandlers = mkPluginHandler STextDocumentCodeLens codeLensProvider     , pluginCommands = [PluginCommand (CommandId typeLensCommandId) "adds a signature" commandHandler]-    , pluginRules = rules+    , pluginRules = rules recorder     , pluginConfigDescriptor = defaultConfigDescriptor {configCustomConfig = mkCustomConfig properties}     } @@ -239,9 +250,9 @@  type instance RuleResult GetGlobalBindingTypeSigs = GlobalBindingTypeSigsResult -rules :: Rules ()-rules = do-  define $ \GetGlobalBindingTypeSigs nfp -> do+rules :: Recorder (WithPriority Log) -> Rules ()+rules recorder = do+  define (cmapWithPrio LogShake recorder) $ \GetGlobalBindingTypeSigs nfp -> do     tmr <- use TypeCheck nfp     -- we need session here for tidying types     hsc <- use GhcSession nfp
src/Development/IDE/Spans/AtPoint.hs view
@@ -17,7 +17,7 @@   , computeTypeReferences   , FOIReferences(..)   , defRowToSymbolInfo-  , getAstNamesAtPoint+  , getNamesAtPoint   , toCurrentLocation   , rowToLoc   ) where@@ -34,6 +34,7 @@ import qualified Development.IDE.GHC.Compat.Util      as Util import           Development.IDE.Spans.Common import           Development.IDE.Types.Options+import           Development.IDE.GHC.Util             (printOutputable)  import           Control.Applicative import           Control.Monad.Extra@@ -86,7 +87,7 @@   case HM.lookup file asts of     Nothing -> ([],[],[])     Just (HAR _ hf _ _ _,mapping) ->-      let names = getAstNamesAtPoint hf pos mapping+      let names = getNamesAtPoint hf pos mapping           adjustedLocs = HM.foldr go [] asts           go (HAR _ _ rf tr _, mapping) xs = refs ++ typerefs ++ xs             where@@ -96,8 +97,8 @@                    $ concat $ mapMaybe (`M.lookup` tr) names         in (names, adjustedLocs,map fromNormalizedFilePath $ HM.keys asts) -getAstNamesAtPoint :: HieASTs a -> Position -> PositionMapping -> [Name]-getAstNamesAtPoint hf pos mapping =+getNamesAtPoint :: HieASTs a -> Position -> PositionMapping -> [Name]+getNamesAtPoint hf pos mapping =   concat $ pointCommand hf posFile (rights . M.keys . getNodeIds)     where       posFile = fromMaybe pos $ fromCurrentPosition mapping pos@@ -229,13 +230,13 @@         prettyNames :: [T.Text]         prettyNames = map prettyName names         prettyName (Right n, dets) = T.unlines $-          wrapHaskell (showNameWithoutUniques n <> maybe "" (" :: " <>) ((prettyType <$> identType dets) <|> maybeKind))+          wrapHaskell (printOutputable n <> maybe "" (" :: " <>) ((prettyType <$> identType dets) <|> maybeKind))           : definedAt n           ++ maybeToList (prettyPackageName n)           ++ catMaybes [ T.unlines . spanDocToMarkdown <$> lookupNameEnv dm n                        ]-          where maybeKind = fmap showGhc $ safeTyThingType =<< lookupNameEnv km n-        prettyName (Left m,_) = showGhc m+          where maybeKind = fmap printOutputable $ safeTyThingType =<< lookupNameEnv km n+        prettyName (Left m,_) = printOutputable m          prettyPackageName n = do           m <- nameModule_maybe n@@ -247,15 +248,15 @@          prettyTypes = map (("_ :: "<>) . prettyType) types         prettyType t = case kind of-          HieFresh -> showGhc t-          HieFromDisk full_file -> showGhc $ hieTypeToIface $ recoverFullType t (hie_types full_file)+          HieFresh -> printOutputable t+          HieFromDisk full_file -> printOutputable $ hieTypeToIface $ recoverFullType t (hie_types full_file)          definedAt name =           -- do not show "at <no location info>" and similar messages           -- see the code of 'pprNameDefnLoc' for more information           case nameSrcLoc name of             UnhelpfulLoc {} | isInternalName name || isSystemName name -> []-            _ -> ["*Defined " <> T.pack (showSDocUnsafe $ pprNameDefnLoc name) <> "*"]+            _ -> ["*Defined " <> printOutputable (pprNameDefnLoc name) <> "*"]  typeLocationsAtPoint   :: forall m@@ -380,7 +381,7 @@  defRowToSymbolInfo :: Res DefRow -> Maybe SymbolInformation defRowToSymbolInfo (DefRow{..}:.(modInfoSrcFile -> Just srcFile))-  = Just $ SymbolInformation (showGhc defNameOcc) kind Nothing Nothing loc Nothing+  = Just $ SymbolInformation (printOutputable defNameOcc) kind Nothing Nothing loc Nothing   where     kind       | isVarOcc defNameOcc = SkVariable
src/Development/IDE/Spans/Common.hs view
@@ -3,9 +3,7 @@ {-# LANGUAGE DerivingStrategies #-}  module Development.IDE.Spans.Common (-  showGhc-, showNameWithoutUniques-, unqualIEWrapName+  unqualIEWrapName , safeTyThingId , safeTyThingType , SpanDoc(..)@@ -34,18 +32,9 @@ type DocMap = NameEnv SpanDoc type KindMap = NameEnv TyThing -showGhc :: Outputable a => a -> T.Text-showGhc = showSD . ppr--showSD :: SDoc -> T.Text-showSD = T.pack . unsafePrintSDoc--showNameWithoutUniques :: Outputable a => a -> T.Text-showNameWithoutUniques = T.pack . printNameWithoutUniques- -- | Shows IEWrappedName, without any modifier, qualifier or unique identifier. unqualIEWrapName :: IEWrappedName RdrName -> T.Text-unqualIEWrapName = showNameWithoutUniques . rdrNameOcc . ieWrappedName+unqualIEWrapName = printOutputable . rdrNameOcc . ieWrappedName  -- From haskell-ide-engine/src/Haskell/Ide/Engine/Support/HieExtras.hs safeTyThingType :: TyThing -> Maybe Type@@ -62,7 +51,9 @@ -- Possible documentation for an element in the code data SpanDoc   = SpanDocString HsDocString SpanDocUris-  | SpanDocText   [T.Text] SpanDocUris+    -- ^ Extern module doc+  | SpanDocText   [T.Text] SpanDocUris+    -- ^ Local module doc   deriving stock (Eq, Show, Generic)   deriving anyclass NFData @@ -76,13 +67,33 @@ emptySpanDoc :: SpanDoc emptySpanDoc = SpanDocText [] (SpanDocUris Nothing Nothing) +-- | Convert `SpanDoc` to Markdown format.+--+-- Return a list `Text` includes haddock, document uri and source code uri,+-- each item can be empty and must end with '\\n' if exist. This is to prevent+-- subsequent render problem caused by the missing newline.+--+-- Example:+--+-- For return value ["xxxx","yyyy"], if we concat the list with inserting+-- a separate line(note by "---\n"),+-- it will result "xxxx---\nyyyy" and can't be rendered as a normal doc.+-- Therefore we check every item in the value to make sure they all end with '\\n',+-- this makes "xxxx\n---\nyyy\n" and can be rendered correctly. spanDocToMarkdown :: SpanDoc -> [T.Text]-spanDocToMarkdown (SpanDocString docs uris)-  = [T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs]-    <> ["\n"] <> spanDocUrisToMarkdown uris-  -- Append the extra newlines since this is markdown --- to get a visible newline,-  -- you need to have two newlines-spanDocToMarkdown (SpanDocText txt uris) = txt <> ["\n"] <> spanDocUrisToMarkdown uris+spanDocToMarkdown = \case+    (SpanDocString docs uris) ->+        let doc = T.pack $ haddockToMarkdown $ H.toRegular $ H._doc $ H.parseParas Nothing $ unpackHDS docs+        in  go [doc] uris+    (SpanDocText txt uris) -> go txt uris+  where+    go [] uris = render <$> spanDocUrisToMarkdown uris+    go txt uris = init txt <> [render (last txt)] <> (render <$> spanDocUrisToMarkdown uris)+    -- If the doc is not end with an '\n', we append it.+    render txt+      | T.null txt = txt+      | T.last txt == '\n' = txt+      | otherwise = txt <> T.pack "\n"  spanDocUrisToMarkdown :: SpanDocUris -> [T.Text] spanDocUrisToMarkdown (SpanDocUris mdoc msrc) = catMaybes
src/Development/IDE/Spans/Documentation.hs view
@@ -1,224 +1,225 @@-{-# LANGUAGE RankNTypes #-}
--- Copyright (c) 2019 The DAML Authors. All rights reserved.
--- SPDX-License-Identifier: Apache-2.0
-
-{-# LANGUAGE CPP        #-}
-
-module Development.IDE.Spans.Documentation (
-    getDocumentation
-  , getDocumentationTryGhc
-  , getDocumentationsTryGhc
-  , DocMap
-  , mkDocMap
-  ) where
-
-import           Control.Monad
-import           Control.Monad.Extra             (findM)
-import           Control.Monad.IO.Class
-import           Data.Either
-import           Data.Foldable
-import           Data.List.Extra
-import qualified Data.Map                        as M
-import           Data.Maybe
-import qualified Data.Set                        as S
-import qualified Data.Text                       as T
-import           Development.IDE.Core.Compile
-import           Development.IDE.Core.RuleTypes
-import           Development.IDE.GHC.Compat
-import           Development.IDE.GHC.Compat.Util
-import           Development.IDE.GHC.Error
-import           Development.IDE.Spans.Common
-import           System.Directory
-import           System.FilePath
-
-import           Language.LSP.Types              (filePathToUri, getUri)
-
-mkDocMap
-  :: HscEnv
-  -> RefMap a
-  -> TcGblEnv
-  -> IO DocAndKindMap
-mkDocMap env rm this_mod =
-  do
-#if MIN_VERSION_ghc(9,2,0)
-     (_ , DeclDocMap this_docs, _) <- extractDocs this_mod
-#else
-     let (_ , DeclDocMap this_docs, _) = extractDocs this_mod
-#endif
-     d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names
-     k <- foldrM getType (tcg_type_env this_mod) names
-     pure $ DKMap d k
-  where
-    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 n
-      pure $ extendNameEnv map n doc
-    getType n map
-      | isTcOcc $ occName n = do
-        kind <- lookupKind env mod n
-        pure $ maybe map (extendNameEnv map n) kind
-      | otherwise = pure map
-    names = rights $ S.toList idents
-    idents = M.keysSet rm
-    mod = tcg_mod this_mod
-
-lookupKind :: HscEnv -> Module -> Name -> IO (Maybe TyThing)
-lookupKind env mod =
-    fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod
-
-getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc
-getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]
-
-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 _    -> return []
-      Right res -> zipWithM unwrap res names
-  where
-    unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n
-    unwrap _ n                      = mkSpanDocText n
-
-    mkSpanDocText name =
-      SpanDocText [] <$> getUris name
-
-    -- Get the uris to the documentation and source html pages if they exist
-    getUris name = do
-      (docFu, srcFu) <-
-        case nameModule_maybe name of
-          Just mod -> liftIO $ do
-            doc <- toFileUriText $ lookupDocHtmlForModule env mod
-            src <- toFileUriText $ lookupSrcHtmlForModule env mod
-            return (doc, src)
-          Nothing -> pure (Nothing, Nothing)
-      let docUri = (<> "#" <> selector <> showNameWithoutUniques name) <$> docFu
-          srcUri = (<> "#" <> showNameWithoutUniques name) <$> srcFu
-          selector
-            | isValName name = "v:"
-            | otherwise = "t:"
-      return $ SpanDocUris docUri srcUri
-
-    toFileUriText = (fmap . fmap) (getUri . filePathToUri)
-
-getDocumentation
- :: HasSrcSpan name
- => [ParsedModule] -- ^ All of the possible modules it could be defined in.
- ->  name -- ^ The name you want documentation for.
- -> [T.Text]
--- This finds any documentation between the name you want
--- documentation for and the one before it. This is only an
--- approximately correct algorithm and there are easily constructed
--- cases where it will be wrong (if so then usually slightly but there
--- may be edge cases where it is very wrong).
--- TODO : Build a version of GHC exactprint to extract this information
--- more accurately.
--- TODO : Implement this for GHC 9.2 with in-tree annotations
---        (alternatively, just remove it and rely soley on GHC's parsing)
-getDocumentation sources targetName = fromMaybe [] $ do
-#if MIN_VERSION_ghc(9,2,0)
-  Nothing
-#else
-  -- Find the module the target is defined in.
-  targetNameSpan <- realSpan $ getLoc targetName
-  tc <-
-    find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)
-      $ reverse sources -- TODO : Is reversing the list here really neccessary?
-
-  -- Top level names bound by the module
-  let bs = [ n | let L _ HsModule{hsmodDecls} = pm_parsed_source tc
-           , L _ (ValD _ hsbind) <- hsmodDecls
-           , Just n <- [name_of_bind hsbind]
-           ]
-  -- Sort the names' source spans.
-  let sortedSpans = sortedNameSpans bs
-  -- Now go ahead and extract the docs.
-  let docs = ann tc
-  nameInd <- elemIndex targetNameSpan sortedSpans
-  let prevNameSpan =
-        if nameInd >= 1
-        then sortedSpans !! (nameInd - 1)
-        else zeroSpan $ srcSpanFile targetNameSpan
-  -- Annoyingly "-- |" documentation isn't annotated with a location,
-  -- so you have to pull it out from the elements.
-  pure
-      $ docHeaders
-      $ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)
-      $ fold
-      docs
-  where
-    -- Get the name bound by a binding. We only concern ourselves with
-    -- @FunBind@ (which covers functions and variables).
-    name_of_bind :: HsBind GhcPs -> Maybe (Located RdrName)
-    name_of_bind FunBind {fun_id} = Just fun_id
-    name_of_bind _                = Nothing
-    -- Get source spans from names, discard unhelpful spans, remove
-    -- duplicates and sort.
-    sortedNameSpans :: [Located RdrName] -> [RealSrcSpan]
-    sortedNameSpans ls = nubSort (mapMaybe (realSpan . getLoc) ls)
-    isBetween target before after = before <= target && target <= after
-#if MIN_VERSION_ghc(9,0,0)
-    ann = apiAnnComments . pm_annotations
-#else
-    ann = fmap filterReal . snd . pm_annotations
-    filterReal :: [Located a] -> [RealLocated a]
-    filterReal = mapMaybe (\(L l v) -> (`L`v) <$> realSpan l)
-#endif
-    annotationFileName :: ParsedModule -> Maybe FastString
-    annotationFileName = fmap srcSpanFile . listToMaybe . map getRealSrcSpan . fold . ann
-
--- | Shows this part of the documentation
-docHeaders :: [RealLocated AnnotationComment]
-           -> [T.Text]
-docHeaders = mapMaybe (\(L _ x) -> wrk x)
-  where
-  wrk = \case
-    -- When `Opt_Haddock` is enabled.
-    AnnDocCommentNext s -> Just $ T.pack s
-    -- When `Opt_KeepRawTokenStream` enabled.
-    AnnLineComment s  -> if "-- |" `isPrefixOf` s
-                            then Just $ T.pack s
-                            else Nothing
-    _ -> Nothing
-#endif
-
--- These are taken from haskell-ide-engine's Haddock plugin
-
--- | Given a module finds the local @doc/html/Foo-Bar-Baz.html@ page.
--- An example for a cabal installed module:
--- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/Data-Vector-Primitive.html@
-lookupDocHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)
-lookupDocHtmlForModule =
-  lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> modDocName <.> "html")
-
--- | Given a module finds the hyperlinked source @doc/html/src/Foo.Bar.Baz.html@ page.
--- An example for a cabal installed module:
--- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/src/Data.Vector.Primitive.html@
-lookupSrcHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)
-lookupSrcHtmlForModule =
-  lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> "src" </> modDocName <.> "html")
-
-lookupHtmlForModule :: (FilePath -> FilePath -> FilePath) -> HscEnv -> Module -> IO (Maybe FilePath)
-lookupHtmlForModule mkDocPath hscEnv m = do
-  -- try all directories
-  let mfs = fmap (concatMap go) (lookupHtmls hscEnv ui)
-  html <- findM doesFileExist (concat . maybeToList $ mfs)
-  -- canonicalize located html to remove /../ indirection which can break some clients
-  -- (vscode on Windows at least)
-  traverse canonicalizePath html
-  where
-    go pkgDocDir = map (mkDocPath pkgDocDir) mns
-    ui = moduleUnit m
-    -- try to locate html file from most to least specific name e.g.
-    --  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
-      map (`intercalate` chunks) [".", "-"]
-
-lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath]
-lookupHtmls df ui =
-  -- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path
-  -- and therefore doesn't expand $topdir on Windows
-  map takeDirectory . unitHaddockInterfaces <$> lookupUnit df ui
+{-# LANGUAGE RankNTypes #-}+-- Copyright (c) 2019 The DAML Authors. All rights reserved.+-- SPDX-License-Identifier: Apache-2.0++{-# LANGUAGE CPP        #-}++module Development.IDE.Spans.Documentation (+    getDocumentation+  , getDocumentationTryGhc+  , getDocumentationsTryGhc+  , DocMap+  , mkDocMap+  ) where++import           Control.Monad+import           Control.Monad.Extra             (findM)+import           Control.Monad.IO.Class+import           Data.Either+import           Data.Foldable+import           Data.List.Extra+import qualified Data.Map                        as M+import           Data.Maybe+import qualified Data.Set                        as S+import qualified Data.Text                       as T+import           Development.IDE.Core.Compile+import           Development.IDE.Core.RuleTypes+import           Development.IDE.GHC.Compat+import           Development.IDE.GHC.Compat.Util+import           Development.IDE.GHC.Error+import           Development.IDE.GHC.Util        (printOutputable)+import           Development.IDE.Spans.Common+import           System.Directory+import           System.FilePath++import           Language.LSP.Types              (filePathToUri, getUri)++mkDocMap+  :: HscEnv+  -> RefMap a+  -> TcGblEnv+  -> IO DocAndKindMap+mkDocMap env rm this_mod =+  do+#if MIN_VERSION_ghc(9,2,0)+     (_ , DeclDocMap this_docs, _) <- extractDocs this_mod+#else+     let (_ , DeclDocMap this_docs, _) = extractDocs this_mod+#endif+     d <- foldrM getDocs (mkNameEnv $ M.toList $ fmap (`SpanDocString` SpanDocUris Nothing Nothing) this_docs) names+     k <- foldrM getType (tcg_type_env this_mod) names+     pure $ DKMap d k+  where+    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 n+      pure $ extendNameEnv map n doc+    getType n map+      | isTcOcc $ occName n = do+        kind <- lookupKind env mod n+        pure $ maybe map (extendNameEnv map n) kind+      | otherwise = pure map+    names = rights $ S.toList idents+    idents = M.keysSet rm+    mod = tcg_mod this_mod++lookupKind :: HscEnv -> Module -> Name -> IO (Maybe TyThing)+lookupKind env mod =+    fmap (fromRight Nothing) . catchSrcErrors (hsc_dflags env) "span" . lookupName env mod++getDocumentationTryGhc :: HscEnv -> Module -> Name -> IO SpanDoc+getDocumentationTryGhc env mod n = head <$> getDocumentationsTryGhc env mod [n]++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 _    -> return []+      Right res -> zipWithM unwrap res names+  where+    unwrap (Right (Just docs, _)) n = SpanDocString docs <$> getUris n+    unwrap _ n                      = mkSpanDocText n++    mkSpanDocText name =+      SpanDocText [] <$> getUris name++    -- Get the uris to the documentation and source html pages if they exist+    getUris name = do+      (docFu, srcFu) <-+        case nameModule_maybe name of+          Just mod -> liftIO $ do+            doc <- toFileUriText $ lookupDocHtmlForModule env mod+            src <- toFileUriText $ lookupSrcHtmlForModule env mod+            return (doc, src)+          Nothing -> pure (Nothing, Nothing)+      let docUri = (<> "#" <> selector <> printOutputable name) <$> docFu+          srcUri = (<> "#" <> printOutputable name) <$> srcFu+          selector+            | isValName name = "v:"+            | otherwise = "t:"+      return $ SpanDocUris docUri srcUri++    toFileUriText = (fmap . fmap) (getUri . filePathToUri)++getDocumentation+ :: HasSrcSpan name+ => [ParsedModule] -- ^ All of the possible modules it could be defined in.+ ->  name -- ^ The name you want documentation for.+ -> [T.Text]+-- This finds any documentation between the name you want+-- documentation for and the one before it. This is only an+-- approximately correct algorithm and there are easily constructed+-- cases where it will be wrong (if so then usually slightly but there+-- may be edge cases where it is very wrong).+-- TODO : Build a version of GHC exactprint to extract this information+-- more accurately.+-- TODO : Implement this for GHC 9.2 with in-tree annotations+--        (alternatively, just remove it and rely soley on GHC's parsing)+getDocumentation sources targetName = fromMaybe [] $ do+#if MIN_VERSION_ghc(9,2,0)+  Nothing+#else+  -- Find the module the target is defined in.+  targetNameSpan <- realSpan $ getLoc targetName+  tc <-+    find ((==) (Just $ srcSpanFile targetNameSpan) . annotationFileName)+      $ reverse sources -- TODO : Is reversing the list here really neccessary?++  -- Top level names bound by the module+  let bs = [ n | let L _ HsModule{hsmodDecls} = pm_parsed_source tc+           , L _ (ValD _ hsbind) <- hsmodDecls+           , Just n <- [name_of_bind hsbind]+           ]+  -- Sort the names' source spans.+  let sortedSpans = sortedNameSpans bs+  -- Now go ahead and extract the docs.+  let docs = ann tc+  nameInd <- elemIndex targetNameSpan sortedSpans+  let prevNameSpan =+        if nameInd >= 1+        then sortedSpans !! (nameInd - 1)+        else zeroSpan $ srcSpanFile targetNameSpan+  -- Annoyingly "-- |" documentation isn't annotated with a location,+  -- so you have to pull it out from the elements.+  pure+      $ docHeaders+      $ filter (\(L target _) -> isBetween target prevNameSpan targetNameSpan)+      $ fold+      docs+  where+    -- Get the name bound by a binding. We only concern ourselves with+    -- @FunBind@ (which covers functions and variables).+    name_of_bind :: HsBind GhcPs -> Maybe (Located RdrName)+    name_of_bind FunBind {fun_id} = Just fun_id+    name_of_bind _                = Nothing+    -- Get source spans from names, discard unhelpful spans, remove+    -- duplicates and sort.+    sortedNameSpans :: [Located RdrName] -> [RealSrcSpan]+    sortedNameSpans ls = nubSort (mapMaybe (realSpan . getLoc) ls)+    isBetween target before after = before <= target && target <= after+#if MIN_VERSION_ghc(9,0,0)+    ann = apiAnnComments . pm_annotations+#else+    ann = fmap filterReal . snd . pm_annotations+    filterReal :: [Located a] -> [RealLocated a]+    filterReal = mapMaybe (\(L l v) -> (`L`v) <$> realSpan l)+#endif+    annotationFileName :: ParsedModule -> Maybe FastString+    annotationFileName = fmap srcSpanFile . listToMaybe . map getRealSrcSpan . fold . ann++-- | Shows this part of the documentation+docHeaders :: [RealLocated AnnotationComment]+           -> [T.Text]+docHeaders = mapMaybe (\(L _ x) -> wrk x)+  where+  wrk = \case+    -- When `Opt_Haddock` is enabled.+    AnnDocCommentNext s -> Just $ T.pack s+    -- When `Opt_KeepRawTokenStream` enabled.+    AnnLineComment s  -> if "-- |" `isPrefixOf` s+                            then Just $ T.pack s+                            else Nothing+    _ -> Nothing+#endif++-- These are taken from haskell-ide-engine's Haddock plugin++-- | Given a module finds the local @doc/html/Foo-Bar-Baz.html@ page.+-- An example for a cabal installed module:+-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/Data-Vector-Primitive.html@+lookupDocHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)+lookupDocHtmlForModule =+  lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> modDocName <.> "html")++-- | Given a module finds the hyperlinked source @doc/html/src/Foo.Bar.Baz.html@ page.+-- An example for a cabal installed module:+-- @~/.cabal/store/ghc-8.10.1/vctr-0.12.1.2-98e2e861/share/doc/html/src/Data.Vector.Primitive.html@+lookupSrcHtmlForModule :: HscEnv -> Module -> IO (Maybe FilePath)+lookupSrcHtmlForModule =+  lookupHtmlForModule (\pkgDocDir modDocName -> pkgDocDir </> "src" </> modDocName <.> "html")++lookupHtmlForModule :: (FilePath -> FilePath -> FilePath) -> HscEnv -> Module -> IO (Maybe FilePath)+lookupHtmlForModule mkDocPath hscEnv m = do+  -- try all directories+  let mfs = fmap (concatMap go) (lookupHtmls hscEnv ui)+  html <- findM doesFileExist (concat . maybeToList $ mfs)+  -- canonicalize located html to remove /../ indirection which can break some clients+  -- (vscode on Windows at least)+  traverse canonicalizePath html+  where+    go pkgDocDir = map (mkDocPath pkgDocDir) mns+    ui = moduleUnit m+    -- try to locate html file from most to least specific name e.g.+    --  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+      map (`intercalate` chunks) [".", "-"]++lookupHtmls :: HscEnv -> Unit -> Maybe [FilePath]+lookupHtmls df ui =+  -- use haddockInterfaces instead of haddockHTMLs: GHC treats haddockHTMLs as URL not path+  -- and therefore doesn't expand $topdir on Windows+  map takeDirectory . unitHaddockInterfaces <$> lookupUnit df ui
src/Development/IDE/Spans/LocalBindings.hs view
@@ -1,140 +1,140 @@-{-# LANGUAGE DerivingStrategies #-}
-
-module Development.IDE.Spans.LocalBindings
-  ( Bindings
-  , getLocalScope
-  , getFuzzyScope
-  , getDefiningBindings
-  , getFuzzyDefiningBindings
-  , bindings
-  ) where
-
-import           Control.DeepSeq
-import           Control.Monad
-import           Data.Bifunctor
-import           Data.IntervalMap.FingerTree    (Interval (..), IntervalMap)
-import qualified Data.IntervalMap.FingerTree    as IM
-import qualified Data.List                      as L
-import qualified Data.Map                       as M
-import qualified Data.Set                       as S
-import           Development.IDE.GHC.Compat     (Name, NameEnv, RealSrcSpan,
-                                                 RefMap, Scope (..), Type,
-                                                 getBindSiteFromContext,
-                                                 getScopeFromContext, identInfo,
-                                                 identType, isSystemName,
-                                                 nameEnvElts, realSrcSpanEnd,
-                                                 realSrcSpanStart, unitNameEnv)
-
-import           Development.IDE.GHC.Error
-import           Development.IDE.Types.Location
-
-------------------------------------------------------------------------------
--- | Turn a 'RealSrcSpan' into an 'Interval'.
-realSrcSpanToInterval :: RealSrcSpan -> Interval Position
-realSrcSpanToInterval rss =
-  Interval
-    (realSrcLocToPosition $ realSrcSpanStart rss)
-    (realSrcLocToPosition $ realSrcSpanEnd   rss)
-
-bindings :: RefMap Type -> Bindings
-bindings = uncurry Bindings . localBindings
-
-------------------------------------------------------------------------------
--- | Compute which identifiers are in scope at every point in the AST. Use
--- 'getLocalScope' to find the results.
-localBindings
-    :: RefMap Type
-    -> ( IntervalMap Position (NameEnv (Name, Maybe Type))
-       , IntervalMap Position (NameEnv (Name, Maybe Type))
-       )
-localBindings refmap = bimap mk mk $ unzip $ do
-  (ident, refs)      <- M.toList refmap
-  Right name         <- pure ident
-  (_, ident_details) <- refs
-  let ty = identType ident_details
-  info <- S.toList $ identInfo ident_details
-  pure
-    ( do
-        Just scopes <- pure $ getScopeFromContext info
-        scope <- scopes >>= \case
-          LocalScope scope -> pure $ realSrcSpanToInterval scope
-          _                -> []
-        pure ( scope
-            , unitNameEnv name (name,ty)
-            )
-    , do
-        Just scope <- pure $ getBindSiteFromContext info
-        pure ( realSrcSpanToInterval scope
-            , unitNameEnv name (name,ty)
-            )
-    )
-  where
-    mk = L.foldl' (flip (uncurry IM.insert)) mempty . join
-
-------------------------------------------------------------------------------
--- | The available bindings at every point in a Haskell tree.
-data Bindings = Bindings
-  { getLocalBindings
-        :: IntervalMap Position (NameEnv (Name, Maybe Type))
-  , getBindingSites
-        :: IntervalMap Position (NameEnv (Name, Maybe Type))
-  }
-
-instance Semigroup Bindings where
-  Bindings a1 b1 <> Bindings a2 b2
-    = Bindings (a1 <> a2) (b1 <> b2)
-
-instance Monoid Bindings where
-  mempty = Bindings mempty mempty
-
-instance NFData Bindings where
-    rnf = rwhnf
-
-instance Show Bindings where
-    show _ = "<bindings>"
-
-
-------------------------------------------------------------------------------
--- | Given a 'Bindings' get every identifier in scope at the given
--- 'RealSrcSpan',
-getLocalScope :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]
-getLocalScope bs rss
-  = nameEnvElts
-  $ foldMap snd
-  $ IM.dominators (realSrcSpanToInterval rss)
-  $ getLocalBindings bs
-
-------------------------------------------------------------------------------
--- | Given a 'Bindings', get every binding currently active at a given
--- 'RealSrcSpan',
-getDefiningBindings :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]
-getDefiningBindings bs rss
-  = nameEnvElts
-  $ foldMap snd
-  $ IM.dominators (realSrcSpanToInterval rss)
-  $ getBindingSites bs
-
-
--- | Lookup all names in scope in any span that intersects the interval
--- defined by the two positions.
--- This is meant for use with the fuzzy `PositionRange` returned by `PositionMapping`
-getFuzzyScope :: Bindings -> Position -> Position -> [(Name, Maybe Type)]
-getFuzzyScope bs a b
-  = filter (not . isSystemName . fst)
-  $ nameEnvElts
-  $ foldMap snd
-  $ IM.intersections (Interval a b)
-  $ getLocalBindings bs
-
-------------------------------------------------------------------------------
--- | Given a 'Bindings', get every binding that intersects the interval defined
--- by the two positions.
--- This is meant for use with the fuzzy `PositionRange` returned by
--- `PositionMapping`
-getFuzzyDefiningBindings :: Bindings -> Position -> Position -> [(Name, Maybe Type)]
-getFuzzyDefiningBindings bs a b
-  = nameEnvElts
-  $ foldMap snd
-  $ IM.intersections (Interval a b)
-  $ getBindingSites bs
-
+{-# LANGUAGE DerivingStrategies #-}++module Development.IDE.Spans.LocalBindings+  ( Bindings+  , getLocalScope+  , getFuzzyScope+  , getDefiningBindings+  , getFuzzyDefiningBindings+  , bindings+  ) where++import           Control.DeepSeq+import           Control.Monad+import           Data.Bifunctor+import           Data.IntervalMap.FingerTree    (Interval (..), IntervalMap)+import qualified Data.IntervalMap.FingerTree    as IM+import qualified Data.List                      as L+import qualified Data.Map                       as M+import qualified Data.Set                       as S+import           Development.IDE.GHC.Compat     (Name, NameEnv, RealSrcSpan,+                                                 RefMap, Scope (..), Type,+                                                 getBindSiteFromContext,+                                                 getScopeFromContext, identInfo,+                                                 identType, isSystemName,+                                                 nameEnvElts, realSrcSpanEnd,+                                                 realSrcSpanStart, unitNameEnv)++import           Development.IDE.GHC.Error+import           Development.IDE.Types.Location++------------------------------------------------------------------------------+-- | Turn a 'RealSrcSpan' into an 'Interval'.+realSrcSpanToInterval :: RealSrcSpan -> Interval Position+realSrcSpanToInterval rss =+  Interval+    (realSrcLocToPosition $ realSrcSpanStart rss)+    (realSrcLocToPosition $ realSrcSpanEnd   rss)++bindings :: RefMap Type -> Bindings+bindings = uncurry Bindings . localBindings++------------------------------------------------------------------------------+-- | Compute which identifiers are in scope at every point in the AST. Use+-- 'getLocalScope' to find the results.+localBindings+    :: RefMap Type+    -> ( IntervalMap Position (NameEnv (Name, Maybe Type))+       , IntervalMap Position (NameEnv (Name, Maybe Type))+       )+localBindings refmap = bimap mk mk $ unzip $ do+  (ident, refs)      <- M.toList refmap+  Right name         <- pure ident+  (_, ident_details) <- refs+  let ty = identType ident_details+  info <- S.toList $ identInfo ident_details+  pure+    ( do+        Just scopes <- pure $ getScopeFromContext info+        scope <- scopes >>= \case+          LocalScope scope -> pure $ realSrcSpanToInterval scope+          _                -> []+        pure ( scope+            , unitNameEnv name (name,ty)+            )+    , do+        Just scope <- pure $ getBindSiteFromContext info+        pure ( realSrcSpanToInterval scope+            , unitNameEnv name (name,ty)+            )+    )+  where+    mk = L.foldl' (flip (uncurry IM.insert)) mempty . join++------------------------------------------------------------------------------+-- | The available bindings at every point in a Haskell tree.+data Bindings = Bindings+  { getLocalBindings+        :: IntervalMap Position (NameEnv (Name, Maybe Type))+  , getBindingSites+        :: IntervalMap Position (NameEnv (Name, Maybe Type))+  }++instance Semigroup Bindings where+  Bindings a1 b1 <> Bindings a2 b2+    = Bindings (a1 <> a2) (b1 <> b2)++instance Monoid Bindings where+  mempty = Bindings mempty mempty++instance NFData Bindings where+    rnf = rwhnf++instance Show Bindings where+    show _ = "<bindings>"+++------------------------------------------------------------------------------+-- | Given a 'Bindings' get every identifier in scope at the given+-- 'RealSrcSpan',+getLocalScope :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]+getLocalScope bs rss+  = nameEnvElts+  $ foldMap snd+  $ IM.dominators (realSrcSpanToInterval rss)+  $ getLocalBindings bs++------------------------------------------------------------------------------+-- | Given a 'Bindings', get every binding currently active at a given+-- 'RealSrcSpan',+getDefiningBindings :: Bindings -> RealSrcSpan -> [(Name, Maybe Type)]+getDefiningBindings bs rss+  = nameEnvElts+  $ foldMap snd+  $ IM.dominators (realSrcSpanToInterval rss)+  $ getBindingSites bs+++-- | Lookup all names in scope in any span that intersects the interval+-- defined by the two positions.+-- This is meant for use with the fuzzy `PositionRange` returned by `PositionMapping`+getFuzzyScope :: Bindings -> Position -> Position -> [(Name, Maybe Type)]+getFuzzyScope bs a b+  = filter (not . isSystemName . fst)+  $ nameEnvElts+  $ foldMap snd+  $ IM.intersections (Interval a b)+  $ getLocalBindings bs++------------------------------------------------------------------------------+-- | Given a 'Bindings', get every binding that intersects the interval defined+-- by the two positions.+-- This is meant for use with the fuzzy `PositionRange` returned by+-- `PositionMapping`+getFuzzyDefiningBindings :: Bindings -> Position -> Position -> [(Name, Maybe Type)]+getFuzzyDefiningBindings bs a b+  = nameEnvElts+  $ foldMap snd+  $ IM.intersections (Interval a b)+  $ getBindingSites bs+
src/Development/IDE/Spans/Pragmas.hs view
@@ -5,17 +5,19 @@ module Development.IDE.Spans.Pragmas   ( NextPragmaInfo(..)   , LineSplitTextEdits(..)-  , getNextPragmaInfo ) where+  , getNextPragmaInfo+  , insertNewPragma ) where  import           Data.Bits                       (Bits (setBit)) import           Data.Function                   ((&)) import qualified Data.List                       as List import qualified Data.Maybe                      as Maybe-import           Data.Text                       (Text)+import           Data.Text                       (Text, pack) import qualified Data.Text                       as Text import           Development.IDE                 (srcSpanToRange) import           Development.IDE.GHC.Compat import           Development.IDE.GHC.Compat.Util+import           GHC.LanguageExtensions.Type     (Extension) import qualified Language.LSP.Types              as LSP  getNextPragmaInfo :: DynFlags -> Maybe Text -> NextPragmaInfo@@ -28,6 +30,13 @@          ParserStateDone{ nextPragma }    -> nextPragma      | otherwise      -> NextPragmaInfo 0 Nothing++insertNewPragma :: NextPragmaInfo -> Extension -> LSP.TextEdit+insertNewPragma (NextPragmaInfo _ (Just (LineSplitTextEdits ins _))) newPragma = ins { LSP._newText = "{-# LANGUAGE " <> pack (show newPragma) <> " #-}\n" } :: LSP.TextEdit+insertNewPragma (NextPragmaInfo nextPragmaLine _) newPragma =  LSP.TextEdit pragmaInsertRange $ "{-# LANGUAGE " <> pack (show newPragma) <> " #-}\n"+    where+        pragmaInsertPosition = LSP.Position (fromIntegral nextPragmaLine) 0+        pragmaInsertRange = LSP.Range pragmaInsertPosition pragmaInsertPosition  -- Pre-declaration comments parser ----------------------------------------------------- 
src/Development/IDE/Types/Exports.hs view
@@ -22,19 +22,18 @@ import           Data.HashSet                (HashSet) import qualified Data.HashSet                as Set import           Data.Hashable               (Hashable)-import           Data.List                   (isSuffixOf)+import           Data.List                   (isSuffixOf, foldl') import           Data.Text                   (Text, pack) import           Development.IDE.GHC.Compat import           Development.IDE.GHC.Orphans () import           Development.IDE.GHC.Util-import           Development.IDE.Types.Shake (WithHieDb) import           GHC.Generics                (Generic) import           HieDb   data ExportsMap = ExportsMap-    { getExportsMap       :: HashMap IdentifierText (HashSet IdentInfo)-    , getModuleExportsMap :: HashMap ModuleNameText (HashSet IdentInfo)+    { getExportsMap       :: !(HashMap IdentifierText (HashSet IdentInfo))+    , getModuleExportsMap :: !(HashMap ModuleNameText (HashSet IdentInfo))     }     deriving (Show) @@ -135,13 +134,11 @@       concatMap (fmap (second Set.fromList) . unpackAvail getModuleName) (mg_exports mi)  updateExportsMapMg :: [ModGuts] -> ExportsMap -> ExportsMap-updateExportsMapMg modGuts old =-    old' <> new+updateExportsMapMg modGuts old = old' <> new     where         new = createExportsMapMg modGuts         old' = deleteAll old (Map.keys $ getModuleExportsMap new)-        deleteAll = foldr deleteEntriesForModule-+        deleteAll = foldl' (flip deleteEntriesForModule)  createExportsMapTc :: [TcGblEnv] -> ExportsMap createExportsMapTc modIface = do@@ -156,6 +153,8 @@ nonInternalModules :: ModuleName -> Bool nonInternalModules = not . (".Internal" `isSuffixOf`) . moduleNameString +type WithHieDb = forall a. (HieDb -> IO a) -> IO a+ createExportsMapHieDb :: WithHieDb -> IO ExportsMap createExportsMapHieDb withHieDb = do     mods <- withHieDb getAllIndexedMods@@ -179,7 +178,7 @@   | otherwise = const []   where     !mod = pack $ moduleNameString mn-    f id@IdentInfo {..} = (pack (prettyPrint name), moduleNameText,[id])+    f id@IdentInfo {..} = (printOutputable name, moduleNameText,[id])   identInfoToKeyVal :: IdentInfo -> (ModuleNameText, IdentInfo)
src/Development/IDE/Types/HscEnvEq.hs view
@@ -1,153 +1,153 @@-module Development.IDE.Types.HscEnvEq
-(   HscEnvEq,
-    hscEnv, newHscEnvEq,
-    hscEnvWithImportPaths,
-    newHscEnvEqPreserveImportPaths,
-    newHscEnvEqWithImportPaths,
-    envImportPaths,
-    envPackageExports,
-    envVisibleModuleNames,
-    deps
-) where
-
-
-import           Control.Concurrent.Async        (Async, async, waitCatch)
-import           Control.Concurrent.Strict       (modifyVar, newVar)
-import           Control.DeepSeq                 (force)
-import           Control.Exception               (evaluate, mask, throwIO)
-import           Control.Monad.Extra             (eitherM, join, mapMaybeM)
-import           Data.Either                     (fromRight)
-import           Data.Set                        (Set)
-import qualified Data.Set                        as Set
-import           Data.Unique                     (Unique)
-import qualified Data.Unique                     as Unique
-import           Development.IDE.GHC.Compat
-import qualified Development.IDE.GHC.Compat.Util as Maybes
-import           Development.IDE.GHC.Error       (catchSrcErrors)
-import           Development.IDE.GHC.Util        (lookupPackageConfig)
-import           Development.IDE.Graph.Classes
-import           Development.IDE.Types.Exports   (ExportsMap, createExportsMap)
-import           OpenTelemetry.Eventlog          (withSpan)
-import           System.Directory                (makeAbsolute)
-import           System.FilePath
-
--- | An 'HscEnv' with equality. Two values are considered equal
---   if they are created with the same call to 'newHscEnvEq'.
-data HscEnvEq = HscEnvEq
-    { envUnique             :: !Unique
-    , hscEnv                :: !HscEnv
-    , deps                  :: [(UnitId, DynFlags)]
-               -- ^ In memory components for this HscEnv
-               -- This is only used at the moment for the import dirs in
-               -- the DynFlags
-    , envImportPaths        :: Maybe (Set FilePath)
-        -- ^ If Just, import dirs originally configured in this env
-        --   If Nothing, the env import dirs are unaltered
-    , 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'.
-newHscEnvEq :: FilePath -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
-newHscEnvEq cradlePath hscEnv0 deps = do
-    let relativeToCradle = (takeDirectory cradlePath </>)
-        hscEnv = removeImportPaths hscEnv0
-
-    -- Make Absolute since targets are also absolute
-    importPathsCanon <-
-      mapM makeAbsolute $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0)
-
-    newHscEnvEqWithImportPaths (Just $ Set.fromList importPathsCanon) hscEnv deps
-
-newHscEnvEqWithImportPaths :: Maybe (Set FilePath) -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
-newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do
-
-    let dflags = hsc_dflags hscEnv
-
-    envUnique <- Unique.newUnique
-
-    -- it's very important to delay the package exports computation
-    envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do
-        -- compute the package imports
-        let pkgst   = unitState hscEnv
-            depends = explicitUnits pkgst
-            modules =
-                [ m
-                | d        <- depends
-                , Just pkg <- [lookupPackageConfig d hscEnv]
-                , (modName, maybeOtherPkgMod) <- unitExposedModules pkg
-                , let m = case maybeOtherPkgMod of
-                        -- When module is re-exported from another package,
-                        -- the origin module is represented by value in Just
-                        Just otherPkgMod -> otherPkgMod
-                        Nothing          -> mkModule (unitInfoId pkg) modName
-                ]
-
-            doOne m = do
-                modIface <- initIfaceLoad hscEnv $
-                    loadInterface "" m (ImportByUser NotBoot)
-                return $ case modIface of
-                    Maybes.Failed    _r -> Nothing
-                    Maybes.Succeeded mi -> Just mi
-        modIfaces <- mapMaybeM doOne modules
-        return $ createExportsMap modIfaces
-
-    -- similar to envPackageExports, evaluated lazily
-    envVisibleModuleNames <- onceAsync $
-      fromRight Nothing
-        <$> catchSrcErrors
-          dflags
-          "listVisibleModuleNames"
-          (evaluate . force . Just $ listVisibleModuleNames hscEnv)
-
-    return HscEnvEq{..}
-
--- | Wrap an 'HscEnv' into an 'HscEnvEq'.
-newHscEnvEqPreserveImportPaths
-    :: HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq
-newHscEnvEqPreserveImportPaths = newHscEnvEqWithImportPaths Nothing
-
--- | Unwrap the 'HscEnv' with the original import paths.
---   Used only for locating imports
-hscEnvWithImportPaths :: HscEnvEq -> HscEnv
-hscEnvWithImportPaths HscEnvEq{..}
-    | Just imps <- envImportPaths
-    = hscSetFlags (setImportPaths (Set.toList imps) (hsc_dflags hscEnv)) hscEnv
-    | otherwise
-    = hscEnv
-
-removeImportPaths :: HscEnv -> HscEnv
-removeImportPaths hsc = hscSetFlags (setImportPaths [] (hsc_dflags hsc)) hsc
-
-instance Show HscEnvEq where
-  show HscEnvEq{envUnique} = "HscEnvEq " ++ show (Unique.hashUnique envUnique)
-
-instance Eq HscEnvEq where
-  a == b = envUnique a == envUnique b
-
-instance NFData HscEnvEq where
-  rnf (HscEnvEq a b c d _ _) =
-      -- deliberately skip the package exports map and visible module names
-      rnf (Unique.hashUnique a) `seq` b `seq` c `seq` rnf d
-
-instance Hashable HscEnvEq where
-  hashWithSalt s = hashWithSalt s . envUnique
-
--- | 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)
+module Development.IDE.Types.HscEnvEq+(   HscEnvEq,+    hscEnv, newHscEnvEq,+    hscEnvWithImportPaths,+    newHscEnvEqPreserveImportPaths,+    newHscEnvEqWithImportPaths,+    envImportPaths,+    envPackageExports,+    envVisibleModuleNames,+    deps+) where+++import           Control.Concurrent.Async        (Async, async, waitCatch)+import           Control.Concurrent.Strict       (modifyVar, newVar)+import           Control.DeepSeq                 (force)+import           Control.Exception               (evaluate, mask, throwIO)+import           Control.Monad.Extra             (eitherM, join, mapMaybeM)+import           Data.Either                     (fromRight)+import           Data.Set                        (Set)+import qualified Data.Set                        as Set+import           Data.Unique                     (Unique)+import qualified Data.Unique                     as Unique+import           Development.IDE.GHC.Compat+import qualified Development.IDE.GHC.Compat.Util as Maybes+import           Development.IDE.GHC.Error       (catchSrcErrors)+import           Development.IDE.GHC.Util        (lookupPackageConfig)+import           Development.IDE.Graph.Classes+import           Development.IDE.Types.Exports   (ExportsMap, createExportsMap)+import           OpenTelemetry.Eventlog          (withSpan)+import           System.Directory                (makeAbsolute)+import           System.FilePath++-- | An 'HscEnv' with equality. Two values are considered equal+--   if they are created with the same call to 'newHscEnvEq'.+data HscEnvEq = HscEnvEq+    { envUnique             :: !Unique+    , hscEnv                :: !HscEnv+    , deps                  :: [(UnitId, DynFlags)]+               -- ^ In memory components for this HscEnv+               -- This is only used at the moment for the import dirs in+               -- the DynFlags+    , envImportPaths        :: Maybe (Set FilePath)+        -- ^ If Just, import dirs originally configured in this env+        --   If Nothing, the env import dirs are unaltered+    , 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'.+newHscEnvEq :: FilePath -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq+newHscEnvEq cradlePath hscEnv0 deps = do+    let relativeToCradle = (takeDirectory cradlePath </>)+        hscEnv = removeImportPaths hscEnv0++    -- Make Absolute since targets are also absolute+    importPathsCanon <-+      mapM makeAbsolute $ relativeToCradle <$> importPaths (hsc_dflags hscEnv0)++    newHscEnvEqWithImportPaths (Just $ Set.fromList importPathsCanon) hscEnv deps++newHscEnvEqWithImportPaths :: Maybe (Set FilePath) -> HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq+newHscEnvEqWithImportPaths envImportPaths hscEnv deps = do++    let dflags = hsc_dflags hscEnv++    envUnique <- Unique.newUnique++    -- it's very important to delay the package exports computation+    envPackageExports <- onceAsync $ withSpan "Package Exports" $ \_sp -> do+        -- compute the package imports+        let pkgst   = unitState hscEnv+            depends = explicitUnits pkgst+            modules =+                [ m+                | d        <- depends+                , Just pkg <- [lookupPackageConfig d hscEnv]+                , (modName, maybeOtherPkgMod) <- unitExposedModules pkg+                , let m = case maybeOtherPkgMod of+                        -- When module is re-exported from another package,+                        -- the origin module is represented by value in Just+                        Just otherPkgMod -> otherPkgMod+                        Nothing          -> mkModule (unitInfoId pkg) modName+                ]++            doOne m = do+                modIface <- initIfaceLoad hscEnv $+                    loadInterface "" m (ImportByUser NotBoot)+                return $ case modIface of+                    Maybes.Failed    _r -> Nothing+                    Maybes.Succeeded mi -> Just mi+        modIfaces <- mapMaybeM doOne modules+        return $ createExportsMap modIfaces++    -- similar to envPackageExports, evaluated lazily+    envVisibleModuleNames <- onceAsync $+      fromRight Nothing+        <$> catchSrcErrors+          dflags+          "listVisibleModuleNames"+          (evaluate . force . Just $ listVisibleModuleNames hscEnv)++    return HscEnvEq{..}++-- | Wrap an 'HscEnv' into an 'HscEnvEq'.+newHscEnvEqPreserveImportPaths+    :: HscEnv -> [(UnitId, DynFlags)] -> IO HscEnvEq+newHscEnvEqPreserveImportPaths = newHscEnvEqWithImportPaths Nothing++-- | Unwrap the 'HscEnv' with the original import paths.+--   Used only for locating imports+hscEnvWithImportPaths :: HscEnvEq -> HscEnv+hscEnvWithImportPaths HscEnvEq{..}+    | Just imps <- envImportPaths+    = hscSetFlags (setImportPaths (Set.toList imps) (hsc_dflags hscEnv)) hscEnv+    | otherwise+    = hscEnv++removeImportPaths :: HscEnv -> HscEnv+removeImportPaths hsc = hscSetFlags (setImportPaths [] (hsc_dflags hsc)) hsc++instance Show HscEnvEq where+  show HscEnvEq{envUnique} = "HscEnvEq " ++ show (Unique.hashUnique envUnique)++instance Eq HscEnvEq where+  a == b = envUnique a == envUnique b++instance NFData HscEnvEq where+  rnf (HscEnvEq a b c d _ _) =+      -- deliberately skip the package exports map and visible module names+      rnf (Unique.hashUnique a) `seq` b `seq` c `seq` rnf d++instance Hashable HscEnvEq where+  hashWithSalt s = hashWithSalt s . envUnique++-- | 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/Logger.hs view
@@ -8,18 +8,68 @@ module Development.IDE.Types.Logger   ( Priority(..)   , Logger(..)-  , logError, logWarning, logInfo, logDebug, logTelemetry+  , Recorder(..)+  , logError, logWarning, logInfo, logDebug   , noLogging+  , WithPriority(..)+  , logWith+  , cmap+  , cmapIO+  , cfilter+  , withDefaultRecorder+  , makeDefaultStderrRecorder+  , priorityToHsLoggerPriority+  , LoggingColumn(..)+  , cmapWithPrio+  , withBacklog+  , lspClientMessageRecorder+  , lspClientLogRecorder+  , module PrettyPrinterModule+  , renderStrict   ) where -import qualified Data.Text as T-+import           Control.Concurrent            (myThreadId)+import           Control.Concurrent.Extra      (Lock, newLock, withLock)+import           Control.Concurrent.STM        (atomically,+                                                newTVarIO, writeTVar, readTVarIO, newTBQueueIO, flushTBQueue, writeTBQueue, isFullTBQueue)+import           Control.Exception             (IOException)+import           Control.Monad                 (forM_, when, (>=>), unless)+import           Control.Monad.IO.Class        (MonadIO (liftIO))+import           Data.Foldable                 (for_)+import           Data.Functor.Contravariant    (Contravariant (contramap))+import           Data.Maybe                    (fromMaybe)+import           Data.Text                     (Text)+import qualified Data.Text                     as T+import qualified Data.Text                     as Text+import qualified Data.Text.IO                  as Text+import           Data.Time                     (defaultTimeLocale, formatTime,+                                                getCurrentTime)+import           GHC.Stack                     (CallStack, HasCallStack,+                                                SrcLoc (SrcLoc, srcLocModule, srcLocStartCol, srcLocStartLine),+                                                callStack, getCallStack,+                                                withFrozenCallStack)+import           Language.LSP.Server+import qualified Language.LSP.Server           as LSP+import           Language.LSP.Types            (LogMessageParams (..),+                                                MessageType (..),+                                                SMethod (SWindowLogMessage, SWindowShowMessage),+                                                ShowMessageParams (..))+import           Prettyprinter                 as PrettyPrinterModule+import           Prettyprinter.Render.Text     (renderStrict)+import           System.IO                     (Handle, IOMode (AppendMode),+                                                hClose, hFlush, hSetEncoding,+                                                openFile, stderr, utf8)+import qualified System.Log.Formatter          as HSL+import qualified System.Log.Handler            as HSL+import qualified System.Log.Handler.Simple     as HSL+import qualified System.Log.Logger             as HsLogger+import           UnliftIO                      (MonadUnliftIO, displayException,+                                                finally, try)  data Priority -- Don't change the ordering of this type or you will mess up the Ord -- instance-    = Telemetry -- ^ Events that are useful for gathering user metrics.-    | Debug -- ^ Verbose debug logging.+    = Debug -- ^ Verbose debug logging.     | Info  -- ^ Useful information in case an error has to be understood.     | Warning       -- ^ These error messages should not occur in a expected usage, and@@ -27,7 +77,6 @@     | Error -- ^ Such log messages must never occur in expected usage.     deriving (Eq, Show, Ord, Enum, Bounded) - -- | Note that this is logging actions _of the program_, not of the user. --   You shouldn't call warning/error if the user has caused an error, only --   if our code has gone wrong and is itself erroneous (e.g. we threw an exception).@@ -51,9 +100,263 @@ logDebug :: Logger -> T.Text -> IO () logDebug x = logPriority x Debug -logTelemetry :: Logger -> T.Text -> IO ()-logTelemetry x = logPriority x Telemetry-- noLogging :: Logger noLogging = Logger $ \_ _ -> return ()++data WithPriority a = WithPriority { priority :: Priority, callStack_ :: CallStack, payload :: a } deriving Functor++-- | Note that this is logging actions _of the program_, not of the user.+--   You shouldn't call warning/error if the user has caused an error, only+--   if our code has gone wrong and is itself erroneous (e.g. we threw an exception).+newtype Recorder msg = Recorder+  { logger_ :: forall m. (MonadIO m) => msg -> m () }++logWith :: (HasCallStack, MonadIO m) => Recorder (WithPriority msg) -> Priority -> msg -> m ()+logWith recorder priority msg = withFrozenCallStack $ logger_ recorder (WithPriority priority callStack msg)++instance Semigroup (Recorder msg) where+  (<>) Recorder{ logger_ = logger_1 } Recorder{ logger_ = logger_2 } =+    Recorder+      { logger_ = \msg -> logger_1 msg >> logger_2 msg }++instance Monoid (Recorder msg) where+  mempty =+    Recorder+      { logger_ = \_ -> pure () }++instance Contravariant Recorder where+  contramap f Recorder{ logger_ } =+    Recorder+      { logger_ = logger_ . f }++cmap :: (a -> b) -> Recorder b -> Recorder a+cmap = contramap++cmapWithPrio :: (a -> b) -> Recorder (WithPriority b) -> Recorder (WithPriority a)+cmapWithPrio f = cmap (fmap f)++cmapIO :: (a -> IO b) -> Recorder b -> Recorder a+cmapIO f Recorder{ logger_ } =+  Recorder+    { logger_ = (liftIO . f) >=> logger_ }++cfilter :: (a -> Bool) -> Recorder a -> Recorder a+cfilter p Recorder{ logger_ } =+  Recorder+    { logger_ = \msg -> when (p msg) (logger_ msg) }++textHandleRecorder :: Handle -> Recorder Text+textHandleRecorder handle =+  Recorder+    { logger_ = \text -> liftIO $ Text.hPutStrLn handle text *> hFlush handle }++-- | Priority is actually for hslogger compatibility+makeDefaultStderrRecorder :: MonadIO m => Maybe [LoggingColumn] -> Priority -> m (Recorder (WithPriority (Doc a)))+makeDefaultStderrRecorder columns minPriority = do+  lock <- liftIO newLock+  makeDefaultHandleRecorder columns minPriority lock stderr++-- | If no path given then use stderr, otherwise use file.+-- Kinda complicated because we also need to setup `hslogger` for+-- `hie-bios` log compatibility reasons. If `hie-bios` can be set to use our+-- logger instead or if `hie-bios` doesn't use `hslogger` then `hslogger` can+-- be removed completely. See `setupHsLogger` comment.+withDefaultRecorder+  :: MonadUnliftIO m+  => Maybe FilePath+  -- ^ Log file path. `Nothing` uses stderr+  -> Maybe [LoggingColumn]+  -- ^ logging columns to display. `Nothing` uses `defaultLoggingColumns`+  -> Priority+  -- ^ min priority for hslogger compatibility+  -> (Recorder (WithPriority (Doc d)) -> m a)+  -- ^ action given a recorder+  -> m a+withDefaultRecorder path columns minPriority action = do+  lock <- liftIO newLock+  let makeHandleRecorder = makeDefaultHandleRecorder columns minPriority lock+  case path of+    Nothing -> do+      recorder <- makeHandleRecorder stderr+      let message = "No log file specified; using stderr."+      logWith recorder Info message+      action recorder+    Just path -> do+      fileHandle :: Either IOException Handle <- liftIO $ try (openFile path AppendMode)+      case fileHandle of+        Left e -> do+          recorder <- makeHandleRecorder stderr+          let exceptionMessage = pretty $ displayException e+          let message = vcat [exceptionMessage, "Couldn't open log file" <+> pretty path <> "; falling back to stderr."]+          logWith recorder Warning message+          action recorder+        Right fileHandle -> finally (makeHandleRecorder fileHandle >>= action) (liftIO $ hClose fileHandle)++makeDefaultHandleRecorder+  :: MonadIO m+  => Maybe [LoggingColumn]+  -- ^ built-in logging columns to display. Nothing uses the default+  -> Priority+  -- ^ min priority for hslogger compatibility+  -> Lock+  -- ^ lock to take when outputting to handle+  -> Handle+  -- ^ handle to output to+  -> m (Recorder (WithPriority (Doc a)))+makeDefaultHandleRecorder columns minPriority lock handle = do+  let Recorder{ logger_ } = textHandleRecorder handle+  let threadSafeRecorder = Recorder { logger_ = \msg -> liftIO $ withLock lock (logger_ msg) }+  let loggingColumns = fromMaybe defaultLoggingColumns columns+  let textWithPriorityRecorder = cmapIO (textWithPriorityToText loggingColumns) threadSafeRecorder+  -- see `setupHsLogger` comment+  liftIO $ setupHsLogger lock handle ["hls", "hie-bios"] (priorityToHsLoggerPriority minPriority)+  pure (cmap docToText textWithPriorityRecorder)+  where+    docToText = fmap (renderStrict . layoutPretty defaultLayoutOptions)++priorityToHsLoggerPriority :: Priority -> HsLogger.Priority+priorityToHsLoggerPriority = \case+  Debug   -> HsLogger.DEBUG+  Info    -> HsLogger.INFO+  Warning -> HsLogger.WARNING+  Error   -> HsLogger.ERROR++-- | The purpose of setting up `hslogger` at all is that `hie-bios` uses+-- `hslogger` to output compilation logs. The easiest way to merge these logs+-- with our log output is to setup an `hslogger` that uses the same handle+-- and same lock as our loggers. That way the output from our loggers and+-- `hie-bios` don't interleave strangely.+-- It may be possible to have `hie-bios` use our logger by decorating the+-- `Cradle.cradleOptsProg.runCradle` we get in the Cradle from+-- `HieBios.findCradle`, but I remember trying that and something not good+-- happened. I'd have to try it again to remember if that was a real issue.+-- Once that is figured out or `hie-bios` doesn't use `hslogger`, then all+-- references to `hslogger` can be removed entirely.+setupHsLogger :: Lock -> Handle -> [String] -> HsLogger.Priority -> IO ()+setupHsLogger lock handle extraLogNames level = do+  hSetEncoding handle utf8++  logH <- HSL.streamHandler handle level++  let logHandle  = logH+        { HSL.writeFunc = \a s -> withLock lock $ HSL.writeFunc logH a s }+      logFormatter  = HSL.tfLogFormatter logDateFormat logFormat+      logHandler = HSL.setFormatter logHandle logFormatter++  HsLogger.updateGlobalLogger HsLogger.rootLoggerName $ HsLogger.setHandlers ([] :: [HSL.GenericHandler Handle])+  HsLogger.updateGlobalLogger "haskell-lsp" $ HsLogger.setHandlers [logHandler]+  HsLogger.updateGlobalLogger "haskell-lsp" $ HsLogger.setLevel level++  -- Also route the additional log names to the same log+  forM_ extraLogNames $ \logName -> do+    HsLogger.updateGlobalLogger logName $ HsLogger.setHandlers [logHandler]+    HsLogger.updateGlobalLogger logName $ HsLogger.setLevel level+  where+    logFormat = "$time [$tid] $prio $loggername:\t$msg"+    logDateFormat = "%Y-%m-%d %H:%M:%S%Q"++data LoggingColumn+  = TimeColumn+  | ThreadIdColumn+  | PriorityColumn+  | DataColumn+  | SourceLocColumn++defaultLoggingColumns :: [LoggingColumn]+defaultLoggingColumns = [TimeColumn, PriorityColumn, DataColumn]++textWithPriorityToText :: [LoggingColumn] -> WithPriority Text -> IO Text+textWithPriorityToText columns WithPriority{ priority, callStack_, payload } = do+    textColumns <- mapM loggingColumnToText columns+    pure $ Text.intercalate " | " textColumns+    where+      showAsText :: Show a => a -> Text+      showAsText = Text.pack . show++      utcTimeToText utcTime = Text.pack $ formatTime defaultTimeLocale "%Y-%m-%dT%H:%M:%S%6QZ" utcTime++      priorityToText :: Priority -> Text+      priorityToText = showAsText++      threadIdToText = showAsText++      callStackToSrcLoc :: CallStack -> Maybe SrcLoc+      callStackToSrcLoc callStack =+        case getCallStack callStack of+          (_, srcLoc) : _ -> Just srcLoc+          _               -> Nothing++      srcLocToText = \case+          Nothing -> "<unknown>"+          Just SrcLoc{ srcLocModule, srcLocStartLine, srcLocStartCol } ->+            Text.pack srcLocModule <> "#" <> showAsText srcLocStartLine <> ":" <> showAsText srcLocStartCol++      loggingColumnToText :: LoggingColumn -> IO Text+      loggingColumnToText = \case+        TimeColumn -> do+          utcTime <- getCurrentTime+          pure (utcTimeToText utcTime)+        SourceLocColumn -> pure $ (srcLocToText . callStackToSrcLoc) callStack_+        ThreadIdColumn -> do+          threadId <- myThreadId+          pure (threadIdToText threadId)+        PriorityColumn -> pure (priorityToText priority)+        DataColumn -> pure payload++-- | Given a 'Recorder' that requires an argument, produces a 'Recorder'+-- that queues up messages until the argument is provided using the callback, at which+-- point it sends the backlog and begins functioning normally.+withBacklog :: (v -> Recorder a) -> IO (Recorder a, v -> IO ())+withBacklog recFun = do+  -- Arbitrary backlog capacity+  backlog <- newTBQueueIO 100+  let backlogRecorder = Recorder $ \it -> liftIO $ atomically $ do+          -- If the queue is full just drop the message on the floor. This is most likely+          -- to happen if the callback is just never going to be called; in which case+          -- we want neither to build up an unbounded backlog in memory, nor block waiting+          -- for space!+          full <- isFullTBQueue backlog+          unless full $ writeTBQueue backlog it++  -- The variable holding the recorder starts out holding the recorder that writes+  -- to the backlog.+  recVar <- newTVarIO backlogRecorder+  -- The callback atomically swaps out the recorder for the final one, and flushes+  -- the backlog to it.+  let cb arg = do+        let recorder = recFun arg+        toRecord <- atomically $ writeTVar recVar recorder >> flushTBQueue backlog+        for_ toRecord (logger_ recorder)++  -- The recorder we actually return looks in the variable and uses whatever is there.+  let varRecorder = Recorder $ \it -> do+          r <- liftIO $ readTVarIO recVar+          logger_ r it++  pure (varRecorder, cb)++-- | Creates a recorder that sends logs to the LSP client via @window/showMessage@ notifications.+lspClientMessageRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text)+lspClientMessageRecorder env = Recorder $ \WithPriority {..} ->+  liftIO $ LSP.runLspT env $ LSP.sendNotification SWindowShowMessage+      ShowMessageParams+        { _xtype = priorityToLsp priority,+          _message = payload+        }++-- | Creates a recorder that sends logs to the LSP client via @window/logMessage@ notifications.+lspClientLogRecorder :: LanguageContextEnv config -> Recorder (WithPriority Text)+lspClientLogRecorder env = Recorder $ \WithPriority {..} ->+  liftIO $ LSP.runLspT env $ LSP.sendNotification SWindowLogMessage+      LogMessageParams+        { _xtype = priorityToLsp priority,+          _message = payload+        }++priorityToLsp :: Priority -> MessageType+priorityToLsp =+  \case+    Debug   -> MtLog+    Info    -> MtInfo+    Warning -> MtWarning+    Error   -> MtError
src/Development/IDE/Types/Options.hs view
@@ -17,7 +17,7 @@   , IdeGhcSession(..)   , OptHaddockParse(..)   , ProgressReportingStyle(..)-  ,optShakeFiles) where+  ) where  import qualified Data.Text                         as T import           Data.Typeable@@ -85,13 +85,6 @@       -- ^ Experimental feature to re-run only the subset of the Shake graph that has changed   } -optShakeFiles :: IdeOptions -> Maybe FilePath-optShakeFiles opts-  | value == defValue = Nothing-  | otherwise = Just value-  where-    value = shakeFiles (optShakeOptions opts)-    defValue = shakeFiles (optShakeOptions $ defaultIdeOptions undefined) data OptHaddockParse = HaddockParse | NoHaddockParse   deriving (Eq,Ord,Show,Enum) @@ -127,9 +120,6 @@     ,optExtensions = ["hs", "lhs"]     ,optPkgLocationOpts = defaultIdePkgLocationOptions     ,optShakeOptions = shakeOptions-        {shakeThreads = 0-        ,shakeFiles = "/dev/null"-        }     ,optShakeProfiling = Nothing     ,optOTMemoryProfiling = IdeOTMemoryProfiling False     ,optReportProgress = IdeReportProgress False
src/Development/IDE/Types/Shake.hs view
@@ -31,21 +31,21 @@ import           Development.IDE.Types.Location import           GHC.Generics import           HieDb.Types                          (HieDb)-import           Language.LSP.Types import qualified StmContainers.Map                    as STM import           Type.Reflection                      (SomeTypeRep (SomeTypeRep),                                                        pattern App, pattern Con,                                                        typeOf, typeRep,                                                        typeRepTyCon) import           Unsafe.Coerce                        (unsafeCoerce)+import Development.IDE.Core.RuleTypes (FileVersion)  -- | Intended to represent HieDb calls wrapped with (currently) retry -- functionality type WithHieDb = forall a. (HieDb -> IO a) -> IO a  data Value v-    = Succeeded TextDocumentVersion v-    | Stale (Maybe PositionDelta) TextDocumentVersion v+    = Succeeded (Maybe FileVersion) v+    | Stale (Maybe PositionDelta) (Maybe FileVersion) v     | Failed Bool -- True if we already tried the persistent rule     deriving (Functor, Generic, Show) 
src/Generics/SYB/GHC.hs view
@@ -1,124 +1,124 @@-{-# LANGUAGE DerivingVia #-}
-{-# LANGUAGE RankNTypes  #-}
-
--- | Custom SYB traversals explicitly designed for operating over the GHC AST.
-module Generics.SYB.GHC
-    ( genericIsSubspan,
-      mkBindListT,
-      everywhereM',
-      smallestM,
-      largestM
-    ) where
-
-import           Control.Monad
-import           Data.Functor.Compose          (Compose (Compose))
-import           Data.Monoid                   (Any (Any))
-import           Development.IDE.GHC.Compat
-import           Development.IDE.Graph.Classes
-import           Generics.SYB
-
-
--- | A generic query intended to be used for calling 'smallestM' and
--- 'largestM'. If the current node is a 'Located', returns whether or not the
--- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which
--- indicates uncertainty. The search strategy in 'smallestM' et al. will
--- continue searching uncertain nodes.
-genericIsSubspan ::
-    forall ast.
-    Typeable ast =>
-    -- | The type of nodes we'd like to consider.
-    Proxy (Located ast) ->
-    SrcSpan ->
-    GenericQ (Maybe (Bool, ast))
-genericIsSubspan _ dst = mkQ Nothing $ \case
-  (L span ast :: Located ast) -> Just (dst `isSubspanOf` span, ast)
-
-
--- | Lift a function that replaces a value with several values into a generic
--- function. The result doesn't perform any searching, so should be driven via
--- 'everywhereM' or friends.
---
--- The 'Int' argument is the index in the list being bound.
-mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m
-mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]
-
-
--- | Apply a monadic transformation everywhere in a top-down manner.
-everywhereM' :: forall m. Monad m => GenericM m -> GenericM m
-everywhereM' f = go
-    where
-        go :: GenericM m
-        go = gmapM go <=< f
-
-
-------------------------------------------------------------------------------
--- Custom SYB machinery
-------------------------------------------------------------------------------
-
--- | Generic monadic transformations that return side-channel data.
-type GenericMQ r m = forall a. Data a => a -> m (r, a)
-
-------------------------------------------------------------------------------
--- | Apply the given 'GenericM' at all every node whose children fail the
--- 'GenericQ', but which passes the query itself.
---
--- The query must be a monotonic function when it returns 'Just'. That is, if
--- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It
--- is the True-to-false edge of the query that triggers the transformation.
---
--- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
--- with data nodes, so for any given node we can only definitely return an
--- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
--- used.
-smallestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m
-smallestM q f = fmap snd . go
-  where
-    go :: GenericMQ Any m
-    go x = do
-      case q x of
-        Nothing -> gmapMQ go x
-        Just (True, a) -> do
-          it@(r, x') <- gmapMQ go x
-          case r of
-            Any True  -> pure it
-            Any False -> fmap (Any True,) $ f a x'
-        Just (False, _) -> pure (mempty, x)
-
-------------------------------------------------------------------------------
--- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but
--- don't descend into children if the query matches. Because this traversal is
--- root-first, this policy will find the largest subtrees for which the query
--- holds true.
---
--- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes
--- with data nodes, so for any given node we can only definitely return an
--- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is
--- used.
-largestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m
-largestM q f = go
-  where
-    go :: GenericM m
-    go x = do
-      case q x of
-        Just (True, a)  -> f a x
-        Just (False, _) -> pure x
-        Nothing         -> gmapM go x
-
-newtype MonadicQuery r m a = MonadicQuery
-  { runMonadicQuery :: m (r, a)
-  }
-  deriving stock (Functor)
-  deriving Applicative via Compose m ((,) r)
-
-
-------------------------------------------------------------------------------
--- | Like 'gmapM', but also returns side-channel data.
-gmapMQ ::
-    forall f r a. (Monoid r, Data a, Applicative f) =>
-    (forall d. Data d => d -> f (r, d)) ->
-    a ->
-    f (r, a)
-gmapMQ f = runMonadicQuery . gfoldl k pure
-  where
-    k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b
-    k c x = c <*> MonadicQuery (f x)
+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE RankNTypes  #-}++-- | Custom SYB traversals explicitly designed for operating over the GHC AST.+module Generics.SYB.GHC+    ( genericIsSubspan,+      mkBindListT,+      everywhereM',+      smallestM,+      largestM+    ) where++import           Control.Monad+import           Data.Functor.Compose          (Compose (Compose))+import           Data.Monoid                   (Any (Any))+import           Development.IDE.GHC.Compat+import           Development.IDE.Graph.Classes+import           Generics.SYB+++-- | A generic query intended to be used for calling 'smallestM' and+-- 'largestM'. If the current node is a 'Located', returns whether or not the+-- given 'SrcSpan' is a subspan. For all other nodes, returns 'Nothing', which+-- indicates uncertainty. The search strategy in 'smallestM' et al. will+-- continue searching uncertain nodes.+genericIsSubspan ::+    forall ast.+    Typeable ast =>+    -- | The type of nodes we'd like to consider.+    Proxy (Located ast) ->+    SrcSpan ->+    GenericQ (Maybe (Bool, ast))+genericIsSubspan _ dst = mkQ Nothing $ \case+  (L span ast :: Located ast) -> Just (dst `isSubspanOf` span, ast)+++-- | Lift a function that replaces a value with several values into a generic+-- function. The result doesn't perform any searching, so should be driven via+-- 'everywhereM' or friends.+--+-- The 'Int' argument is the index in the list being bound.+mkBindListT :: forall b m. (Data b, Monad m) => (Int -> b -> m [b]) -> GenericM m+mkBindListT f = mkM $ fmap join . traverse (uncurry f) . zip [0..]+++-- | Apply a monadic transformation everywhere in a top-down manner.+everywhereM' :: forall m. Monad m => GenericM m -> GenericM m+everywhereM' f = go+    where+        go :: GenericM m+        go = gmapM go <=< f+++------------------------------------------------------------------------------+-- Custom SYB machinery+------------------------------------------------------------------------------++-- | Generic monadic transformations that return side-channel data.+type GenericMQ r m = forall a. Data a => a -> m (r, a)++------------------------------------------------------------------------------+-- | Apply the given 'GenericM' at all every node whose children fail the+-- 'GenericQ', but which passes the query itself.+--+-- The query must be a monotonic function when it returns 'Just'. That is, if+-- @s@ is a subtree of @t@, @q t@ should return @Just True@ if @q s@ does. It+-- is the True-to-false edge of the query that triggers the transformation.+--+-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes+-- with data nodes, so for any given node we can only definitely return an+-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is+-- used.+smallestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m+smallestM q f = fmap snd . go+  where+    go :: GenericMQ Any m+    go x = do+      case q x of+        Nothing -> gmapMQ go x+        Just (True, a) -> do+          it@(r, x') <- gmapMQ go x+          case r of+            Any True  -> pure it+            Any False -> fmap (Any True,) $ f a x'+        Just (False, _) -> pure (mempty, x)++------------------------------------------------------------------------------+-- | Apply the given 'GenericM' at every node that passes the 'GenericQ', but+-- don't descend into children if the query matches. Because this traversal is+-- root-first, this policy will find the largest subtrees for which the query+-- holds true.+--+-- Why is the query a @Maybe Bool@? The GHC AST intersperses 'Located' nodes+-- with data nodes, so for any given node we can only definitely return an+-- answer if it's a 'Located'. See 'genericIsSubspan' for how this parameter is+-- used.+largestM :: forall m a. Monad m => GenericQ (Maybe (Bool, a)) -> (a -> GenericM m) -> GenericM m+largestM q f = go+  where+    go :: GenericM m+    go x = do+      case q x of+        Just (True, a)  -> f a x+        Just (False, _) -> pure x+        Nothing         -> gmapM go x++newtype MonadicQuery r m a = MonadicQuery+  { runMonadicQuery :: m (r, a)+  }+  deriving stock (Functor)+  deriving Applicative via Compose m ((,) r)+++------------------------------------------------------------------------------+-- | Like 'gmapM', but also returns side-channel data.+gmapMQ ::+    forall f r a. (Monoid r, Data a, Applicative f) =>+    (forall d. Data d => d -> f (r, d)) ->+    a ->+    f (r, a)+gmapMQ f = runMonadicQuery . gfoldl k pure+  where+    k :: Data d => MonadicQuery r f (d -> b) -> d -> MonadicQuery r f b+    k c x = c <*> MonadicQuery (f x)
src/Text/Fuzzy/Parallel.hs view
@@ -1,96 +1,91 @@ -- | Parallel versions of 'filter' and 'simpleFilter'+ module Text.Fuzzy.Parallel (   filter,     simpleFilter,-    Scored(..),-    -- reexports-    Fuzzy,+    match,+    Scored(..) ) where -import           Control.Monad.ST            (runST)-import           Control.Parallel.Strategies (Eval, Strategy, evalTraversable,-                                              parTraversable, rseq, using)-import           Data.Monoid.Textual         (TextualMonoid)-import           Data.Vector                 (Vector, (!))-import qualified Data.Vector                 as V--- need to use a stable sort-import           Data.Bifunctor              (second)-import           Data.Char                   (toLower)-import           Data.Maybe                  (fromMaybe)-import qualified Data.Monoid.Textual         as T+import           Control.Parallel.Strategies (rseq, using, parList, evalList)+import           Data.Bits                   ((.|.))+import           Data.Maybe                  (fromMaybe, mapMaybe)+import qualified Data.Text                   as T+import qualified Data.Text.Internal          as T+import qualified Data.Text.Array             as TA import           Prelude                     hiding (filter)-import           Text.Fuzzy                  (Fuzzy (..)) -data Scored a = Scored {score_ :: !Int, original:: !a}-  deriving (Functor,Show)+data Scored a = Scored {score :: !Int, original:: !a}+  deriving (Functor, Show)  -- | Returns the rendered output and the -- matching score for a pattern and a text. -- Two examples are given below: ----- >>> match "fnt" "infinite" "" "" id True--- Just ("infinite",3)+-- >>> match "fnt" "infinite"+-- Just 3 ----- >>> match "hsk" ("Haskell",1995) "<" ">" fst False--- Just ("<h>a<s><k>ell",5)+-- >>> match "hsk" "Haskell"+-- Just 5 -- {-# INLINABLE match #-} -match :: (T.TextualMonoid s)-      => s        -- ^ Pattern in lowercase except for first character-      -> t        -- ^ The value containing the text to search in.-      -> s        -- ^ The text to add before each match.-      -> s        -- ^ The text to add after each match.-      -> (t -> s) -- ^ The function to extract the text from the container.-      -> Maybe (Fuzzy t s) -- ^ The original value, rendered string and score.-match pattern t pre post extract =-    if null pat then Just (Fuzzy t result totalScore) else Nothing+match :: T.Text    -- ^ Pattern in lowercase except for first character+      -> T.Text    -- ^ The text to search in.+      -> Maybe Int -- ^ The score+match (T.Text pArr pOff pLen) (T.Text sArr sOff sLen) = go 0 1 pOff sOff   where-    null :: (T.TextualMonoid s) => s -> Bool-    null = not . T.any (const True)+    pTotal = pOff + pLen+    sDelta = sOff + sLen - pTotal -    s = extract t-    (totalScore, _currScore, result, pat, _) =-      T.foldl'-        undefined-        (\(tot, cur, res, pat, isFirst) c ->-            case T.splitCharacterPrefix pat of-              Nothing -> (tot, 0, res <> T.singleton c, pat, isFirst)-              Just (x, xs) ->-                -- the case of the first character has to match-                -- otherwise use lower case since the pattern is assumed lower-                let !c' = if isFirst then c else toLower c in-                if x == c' then-                  let cur' = cur * 2 + 1 in-                  (tot + cur', cur', res <> pre <> T.singleton c <> post, xs, False)-                else (tot, 0, res <> T.singleton c, pat, isFirst)-        ) ( 0-          , 1 -- matching at the start gives a bonus (cur = 1)-          , mempty, pattern, True) s+    go !totalScore !currScore !currPOff !currSOff+      -- If pattern has been matched in full+      | currPOff >= pTotal+      = Just totalScore+      -- If there is not enough left to match the rest of the pattern, equivalent to+      -- (sOff + sLen - currSOff) < (pOff + pLen - currPOff)+      | currSOff > currPOff + sDelta+      = Nothing+      -- This is slightly broken for non-ASCII:+      -- 1. If code units, consisting a single pattern code point, are found as parts+      --    of different code points, it counts as a match. Unless you use a ton of emojis+      --    as identifiers, such false positives should not be be a big deal,+      --    and anyways HLS does not currently support such use cases, because it uses+      --    code point and UTF-16 code unit positions interchangeably.+      -- 2. Case conversions is not applied to non-ASCII code points, because one has+      --    to call T.toLower (not T.map toLower), reallocating the string in full, which+      --    is too much of performance penalty for fuzzy search. Again, anyway HLS does not+      --    attempt to do justice to Unicode: proper Unicode text matching requires+      --    `unicode-transforms` and friends.+      -- Altogether we sacrifice correctness for the sake of performance, which+      -- is a right trade-off for fuzzy search.+      | pByte <- TA.unsafeIndex pArr currPOff+      , sByte <- TA.unsafeIndex sArr currSOff+      -- First byte (currPOff == pOff) should match exactly, otherwise - up to case.+      , pByte == sByte || (currPOff /= pOff && pByte == toLowerAscii sByte)+      = let curr = currScore * 2 + 1 in+        go (totalScore + curr) curr (currPOff + 1) (currSOff + 1)+      | otherwise+      = go totalScore 0 currPOff (currSOff + 1) +    toLowerAscii w = if (w - 65) < 26 then w .|. 0x20 else w+ -- | The function to filter a list of values by fuzzy search on the text extracted from them.-filter :: (TextualMonoid s)-       => Int      -- ^ Chunk size. 1000 works well.-       -> Int      -- ^ Max. number of results wanted-       -> s        -- ^ Pattern.-       -> [t]      -- ^ The list of values containing the text to search in.-       -> s        -- ^ The text to add before each match.-       -> s        -- ^ The text to add after each match.-       -> (t -> s) -- ^ The function to extract the text from the container.-       -> [Scored t] -- ^ The list of results, sorted, highest score first.-filter chunkSize maxRes pattern ts pre post extract = runST $ do-  let v = V.mapMaybe id-             (V.map (\t -> match pattern' t pre post extract) (V.fromList ts)-             `using`-             parVectorChunk chunkSize (evalTraversable forceScore))-      perfectScore = score $ fromMaybe (error $ T.toString undefined pattern) $-        match pattern' pattern' "" "" id-  return $ partialSortByAscScore maxRes perfectScore v+filter :: Int           -- ^ Chunk size. 1000 works well.+       -> Int           -- ^ Max. number of results wanted+       -> T.Text        -- ^ Pattern.+       -> [t]           -- ^ The list of values containing the text to search in.+       -> (t -> T.Text) -- ^ The function to extract the text from the container.+       -> [Scored t]    -- ^ The list of results, sorted, highest score first.+filter chunkSize maxRes pattern ts extract = partialSortByAscScore maxRes perfectScore (concat vss)   where       -- Preserve case for the first character, make all others lowercase-      pattern' = case T.splitCharacterPrefix pattern of-          Just (c, rest) -> T.singleton c <> T.map toLower rest-          _              -> pattern+      pattern' = case T.uncons pattern of+        Just (c, rest) -> T.cons c (T.toLower rest)+        _              -> pattern+      vss = map (mapMaybe (\t -> flip Scored t <$> match pattern' (extract t))) (chunkList chunkSize ts)+        `using` parList (evalList rseq)+      perfectScore = fromMaybe (error $ T.unpack pattern) $ match pattern' pattern'  -- | Return all elements of the list that have a fuzzy -- match against the pattern. Runs with default settings where@@ -99,84 +94,44 @@ -- >>> simpleFilter "vm" ["vim", "emacs", "virtual machine"] -- ["vim","virtual machine"] {-# INLINABLE simpleFilter #-}-simpleFilter :: (TextualMonoid s)-             => Int -- ^ Chunk size. 1000 works well.-             -> Int -- ^ Max. number of results wanted-             -> s   -- ^ Pattern to look for.-             -> [s] -- ^ List of texts to check.-             -> [Scored s] -- ^ The ones that match.+simpleFilter :: Int      -- ^ Chunk size. 1000 works well.+             -> Int      -- ^ Max. number of results wanted+             -> T.Text   -- ^ Pattern to look for.+             -> [T.Text] -- ^ List of texts to check.+             -> [Scored T.Text] -- ^ The ones that match. simpleFilter chunk maxRes pattern xs =-  filter chunk maxRes pattern xs mempty mempty id-------------------------------------------------------------------------------------- | Evaluation that forces the 'score' field-forceScore :: TextualMonoid s => Fuzzy t s -> Eval(Fuzzy t s)-forceScore it@Fuzzy{score} = do-  score' <- rseq score-  return it{score = score'}+  filter chunk maxRes pattern xs id  -------------------------------------------------------------------------------- --- | Divides a vector in chunks, applies the strategy in parallel to each chunk.-parVectorChunk :: Int -> Strategy a -> Vector a -> Eval (Vector a)-parVectorChunk chunkSize st v =-    V.concat <$> parTraversable (evalTraversable st) (chunkVector chunkSize v)---- >>> chunkVector 3 (V.fromList [0..10])--- >>> chunkVector 3 (V.fromList [0..11])--- >>> chunkVector 3 (V.fromList [0..12])--- [[0,1,2],[3,4,5],[6,7,8],[9,10]]--- [[0,1,2],[3,4,5],[6,7,8],[9,10,11]]--- [[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12]]-chunkVector :: Int -> Vector a -> [Vector a]-chunkVector chunkSize v = do-    let indices = chunkIndices chunkSize (0,V.length v)-    [V.slice l (h-l+1) v | (l,h) <- indices]---- >>> chunkIndices 3 (0,9)--- >>> chunkIndices 3 (0,10)--- >>> chunkIndices 3 (0,11)--- [(0,2),(3,5),(6,8)]--- [(0,2),(3,5),(6,8),(9,9)]--- [(0,2),(3,5),(6,8),(9,10)]-chunkIndices :: Int -> (Int,Int) -> [(Int,Int)]-chunkIndices chunkSize (from,to) =-  map (second pred) $-  pairwise $-  [from, from+chunkSize .. to-1] ++ [to]--pairwise :: [a] -> [(a,a)]-pairwise []       = []-pairwise [_]      = []-pairwise (x:y:xs) = (x,y) : pairwise (y:xs)+chunkList :: Int -> [a] -> [[a]]+chunkList chunkSize = go+  where+    go [] = []+    go xs = ys : go zs+      where+        (ys, zs) = splitAt chunkSize xs  -- | A stable partial sort ascending by score. O(N) best case, O(wanted*N) worst case-partialSortByAscScore :: TextualMonoid s-            => Int  -- ^ Number of items needed+partialSortByAscScore+            :: Int  -- ^ Number of items needed             -> Int  -- ^ Value of a perfect score-            -> Vector (Fuzzy t s)             -> [Scored t]-partialSortByAscScore wantedCount perfectScore v = loop 0 (SortState minBound perfectScore 0) [] where-  l = V.length v-  loop index st@SortState{..} acc+            -> [Scored t]+partialSortByAscScore wantedCount perfectScore orig = loop orig (SortState minBound perfectScore 0) [] where+  loop [] st@SortState{..} acc     | foundCount == wantedCount = reverse acc-    | index == l--- ProgressCancelledException-    = if bestScoreSeen < scoreWanted-        then loop 0 st{scoreWanted = bestScoreSeen, bestScoreSeen = minBound} acc+    | otherwise = if bestScoreSeen < scoreWanted+        then loop orig st{scoreWanted = bestScoreSeen, bestScoreSeen = minBound} acc         else reverse acc-    | otherwise =-      case v!index of-        x | score x == scoreWanted-          -> loop (index+1) st{foundCount = foundCount+1} (toScored x:acc)-          | score x < scoreWanted && score x > bestScoreSeen-          -> loop (index+1) st{bestScoreSeen = score x} acc-          | otherwise-          -> loop (index+1) st acc--toScored :: TextualMonoid s => Fuzzy t s -> Scored t-toScored Fuzzy{..} = Scored score original+  loop (x : xs) st@SortState{..} acc+    | foundCount == wantedCount = reverse acc+    | score x == scoreWanted+    = loop xs st{foundCount = foundCount+1} (x:acc)+    | score x < scoreWanted && score x > bestScoreSeen+    = loop xs st{bestScoreSeen = score x} acc+    | otherwise+    = loop xs st acc  data SortState a = SortState   { bestScoreSeen :: !Int
− test/data/cabal-exe/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-0.1.0.0/x/a/build/a/autogen/Paths_a.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_a (-    version,-    getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,-    getDataFileName, getSysconfDir-  ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [0,1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir     = "/Users/pepeiborra/.cabal/bin"-libdir     = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3/a-0.1.0.0-inplace-a"-dynlibdir  = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3"-datadir    = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.3/a-0.1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.3/a-0.1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "a_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "a_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "a_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "a_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "a_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "a_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do-  dir <- getDataDir-  return (dir ++ "/" ++ name)
test/data/hover/GotoHover.hs view
@@ -61,3 +61,6 @@  hole :: Int hole = _++hole2 :: a -> Maybe a+hole2 = _
test/data/multi/a/A.hs view
@@ -1,3 +1,3 @@ module A(foo) where-+import Control.Concurrent.Async foo = ()
test/data/multi/a/a.cabal view
@@ -4,6 +4,6 @@ cabal-version: >= 1.2  library-  build-depends: base+  build-depends: base, async   exposed-modules: A   hs-source-dirs: .
+ test/data/multi/c/C.hs view
@@ -0,0 +1,3 @@+module C(module C) where+import A+cux = foo
+ test/data/multi/c/c.cabal view
@@ -0,0 +1,9 @@+name: c+version: 1.0.0+build-type: Simple+cabal-version: >= 1.2++library+  build-depends: base, a+  exposed-modules: C+  hs-source-dirs: .
test/data/multi/cabal.project view
@@ -1,1 +1,1 @@-packages: a b+packages: a b c
− test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.2/a-1.0.0/build/autogen/Paths_a.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_a (-    version,-    getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,-    getDataFileName, getSysconfDir-  ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir     = "/Users/pepeiborra/.cabal/bin"-libdir     = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.2/a-1.0.0-inplace"-dynlibdir  = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.2"-datadir    = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.2/a-1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.2/a-1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "a_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "a_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "a_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "a_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "a_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "a_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do-  dir <- getDataDir-  return (dir ++ "/" ++ name)
− test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-1.0.0/build/autogen/Paths_a.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_a (-    version,-    getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,-    getDataFileName, getSysconfDir-  ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir     = "/Users/pepeiborra/.cabal/bin"-libdir     = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3/a-1.0.0-inplace"-dynlibdir  = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3"-datadir    = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.3/a-1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.3/a-1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "a_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "a_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "a_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "a_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "a_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "a_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do-  dir <- getDataDir-  return (dir ++ "/" ++ name)
− test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/b-1.0.0/build/autogen/Paths_b.hs
@@ -1,50 +0,0 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE NoRebindableSyntax #-}-{-# OPTIONS_GHC -fno-warn-missing-import-lists #-}-module Paths_b (-    version,-    getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir,-    getDataFileName, getSysconfDir-  ) where--import qualified Control.Exception as Exception-import Data.Version (Version(..))-import System.Environment (getEnv)-import Prelude--#if defined(VERSION_base)--#if MIN_VERSION_base(4,0,0)-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#else-catchIO :: IO a -> (Exception.Exception -> IO a) -> IO a-#endif--#else-catchIO :: IO a -> (Exception.IOException -> IO a) -> IO a-#endif-catchIO = Exception.catch--version :: Version-version = Version [1,0,0] []-bindir, libdir, dynlibdir, datadir, libexecdir, sysconfdir :: FilePath--bindir     = "/Users/pepeiborra/.cabal/bin"-libdir     = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3/b-1.0.0-inplace"-dynlibdir  = "/Users/pepeiborra/.cabal/lib/x86_64-osx-ghc-8.10.3"-datadir    = "/Users/pepeiborra/.cabal/share/x86_64-osx-ghc-8.10.3/b-1.0.0"-libexecdir = "/Users/pepeiborra/.cabal/libexec/x86_64-osx-ghc-8.10.3/b-1.0.0"-sysconfdir = "/Users/pepeiborra/.cabal/etc"--getBinDir, getLibDir, getDynLibDir, getDataDir, getLibexecDir, getSysconfDir :: IO FilePath-getBinDir = catchIO (getEnv "b_bindir") (\_ -> return bindir)-getLibDir = catchIO (getEnv "b_libdir") (\_ -> return libdir)-getDynLibDir = catchIO (getEnv "b_dynlibdir") (\_ -> return dynlibdir)-getDataDir = catchIO (getEnv "b_datadir") (\_ -> return datadir)-getLibexecDir = catchIO (getEnv "b_libexecdir") (\_ -> return libexecdir)-getSysconfDir = catchIO (getEnv "b_sysconfdir") (\_ -> return sysconfdir)--getDataFileName :: FilePath -> IO FilePath-getDataFileName name = do-  dir <- getDataDir-  return (dir ++ "/" ++ name)
test/data/multi/hie.yaml view
@@ -4,3 +4,5 @@       component: "lib:a"     - path: "./b"       component: "lib:b"+    - path: "./c"+      component: "lib:c"
+ test/exe/FuzzySearch.hs view
@@ -0,0 +1,132 @@+module FuzzySearch (tests) where++import Control.Monad (guard)+import Data.Char (toLower)+import Data.Maybe (catMaybes)+import qualified Data.Monoid.Textual as T+import Data.Text (Text, inits, pack)+import qualified Data.Text as Text+import System.Directory (doesFileExist)+import System.IO.Unsafe (unsafePerformIO)+import System.Info.Extra (isWindows)+import Test.QuickCheck+import Test.Tasty+import Test.Tasty.ExpectedFailure+import Test.Tasty.HUnit+import Test.Tasty.QuickCheck (testProperty)+import Text.Fuzzy (Fuzzy (..))+import qualified Text.Fuzzy as Fuzzy+import Text.Fuzzy.Parallel+import Prelude hiding (filter)++tests :: TestTree+tests =+  testGroup+    "Fuzzy search"+    [ needDictionary $+        testGroup+          "match works as expected on the english dictionary"+          [ testProperty "for legit words" propLegit,+            testProperty "for prefixes" propPrefix,+            testProperty "for typos" propTypo+          ]+    ]++test :: Text -> Bool+test candidate = do+  let previous =+        catMaybes+          [ (d,) . Fuzzy.score+              <$> referenceImplementation candidate d "" "" id+            | d <- dictionary+          ]+      new = catMaybes [(d,) <$> match candidate d | d <- dictionary]+  previous == new++propLegit :: Property+propLegit = forAll (elements dictionary) test++propPrefix :: Property+propPrefix = forAll (elements dictionary >>= elements . inits) test++propTypo :: Property+propTypo = forAll typoGen test++typoGen :: Gen Text+typoGen = do+  w <- elements dictionary+  l <- elements [0 .. Text.length w -1]+  let wl = Text.index w l+  c <- elements [ c | c <- ['a' .. 'z'], c /= wl]+  return $ replaceAt w l c++replaceAt :: Text -> Int -> Char -> Text+replaceAt t i c =+  let (l, r) = Text.splitAt i t+   in l <> Text.singleton c <> r++dictionaryPath :: FilePath+dictionaryPath = "/usr/share/dict/words"++{-# NOINLINE dictionary #-}+dictionary :: [Text]+dictionary = unsafePerformIO $ do+  existsDictionary <- doesFileExist dictionaryPath+  if existsDictionary+    then map pack . words <$> readFile dictionaryPath+    else pure []++referenceImplementation ::+  (T.TextualMonoid s) =>+  -- | Pattern in lowercase except for first character+  s ->+  -- | The value containing the text to search in.+  t ->+  -- | The text to add before each match.+  s ->+  -- | The text to add after each match.+  s ->+  -- | The function to extract the text from the container.+  (t -> s) ->+  -- | The original value, rendered string and score.+  Maybe (Fuzzy t s)+referenceImplementation pattern t pre post extract =+  if null pat then Just (Fuzzy t result totalScore) else Nothing+  where+    null :: (T.TextualMonoid s) => s -> Bool+    null = not . T.any (const True)++    s = extract t+    (totalScore, _currScore, result, pat, _) =+      T.foldl'+        undefined+        ( \(tot, cur, res, pat, isFirst) c ->+            case T.splitCharacterPrefix pat of+              Nothing -> (tot, 0, res <> T.singleton c, pat, isFirst)+              Just (x, xs) ->+                -- the case of the first character has to match+                -- otherwise use lower case since the pattern is assumed lower+                let !c' = if isFirst then c else toLower c+                 in if x == c'+                      then+                        let cur' = cur * 2 + 1+                         in ( tot + cur',+                              cur',+                              res <> pre <> T.singleton c <> post,+                              xs,+                              False+                            )+                      else (tot, 0, res <> T.singleton c, pat, isFirst)+        )+        ( 0,+          1, -- matching at the start gives a bonus (cur = 1)+          mempty,+          pattern,+          True+        )+        s++needDictionary :: TestTree -> TestTree+needDictionary+  | null dictionary = ignoreTestBecause ("not found: " <> dictionaryPath)+  | otherwise = id
test/exe/HieDbRetry.hs view
@@ -5,25 +5,34 @@                                                withVar) import           Control.Exception            (ErrorCall (ErrorCall), evaluate,                                                throwIO, tryJust)-import           Data.Text                    (Text)+import           Control.Monad.IO.Class       (MonadIO (liftIO)) import           Data.Tuple.Extra             (dupe) import qualified Database.SQLite.Simple       as SQLite import           Development.IDE.Session      (retryOnException,                                                retryOnSqliteBusy)-import           Development.IDE.Types.Logger (Logger (Logger), Priority,-                                               noLogging)+import qualified Development.IDE.Session      as Session+import           Development.IDE.Types.Logger (Recorder (Recorder, logger_),+                                               WithPriority (WithPriority, payload),+                                               cmapWithPrio) import qualified System.Random                as Random import           Test.Tasty                   (TestTree, testGroup) import           Test.Tasty.HUnit             (assertFailure, testCase, (@?=)) -makeLogger :: Var [(Priority, Text)] -> Logger-makeLogger msgsVar = Logger $ \priority msg -> modifyVar msgsVar (\msgs -> pure ((priority, msg) : msgs, ()))+data Log+  = LogSession Session.Log+  deriving Show +makeLogger :: Var [Log] -> Recorder (WithPriority Log)+makeLogger msgsVar =+  Recorder {+    logger_ = \WithPriority{ payload = msg } -> liftIO $ modifyVar msgsVar (\msgs -> pure (msg : msgs, ()))+  }+ rng :: Random.StdGen rng = Random.mkStdGen 0 -retryOnSqliteBusyForTest :: Logger -> Int -> IO a -> IO a-retryOnSqliteBusyForTest logger maxRetryCount = retryOnException isErrorBusy logger 1 1 maxRetryCount rng+retryOnSqliteBusyForTest :: Recorder (WithPriority Log) -> Int -> IO a -> IO a+retryOnSqliteBusyForTest recorder maxRetryCount = retryOnException isErrorBusy (cmapWithPrio LogSession recorder) 1 1 maxRetryCount rng  isErrorBusy :: SQLite.SQLError -> Maybe SQLite.SQLError isErrorBusy e@@ -60,7 +69,7 @@       let expected = 1 :: Int       let maxRetryCount = 0 -      actual <- retryOnSqliteBusyForTest noLogging maxRetryCount (pure expected)+      actual <- retryOnSqliteBusyForTest mempty maxRetryCount (pure expected)        actual @?= expected @@ -69,7 +78,7 @@       let maxRetryCount = 3       let incrementThenThrow = modifyVar countVar (\count -> pure (dupe (count + 1))) >> throwIO errorBusy -      _ <- tryJust isErrorBusy (retryOnSqliteBusyForTest noLogging maxRetryCount incrementThenThrow)+      _ <- tryJust isErrorBusy (retryOnSqliteBusyForTest mempty maxRetryCount incrementThenThrow)        withVar countVar $ \count ->         count @?= maxRetryCount + 1@@ -86,7 +95,7 @@               modifyVar countVar (\count -> pure (dupe (count + 1)))  -      _ <- tryJust isErrorCall (retryOnSqliteBusyForTest noLogging maxRetryCount throwThenIncrement)+      _ <- tryJust isErrorCall (retryOnSqliteBusyForTest mempty maxRetryCount throwThenIncrement)        withVar countVar $ \count ->         count @?= 0@@ -101,27 +110,29 @@             else               modifyVar countVar (\count -> pure (dupe (count + 1))) -      _ <- retryOnSqliteBusy noLogging rng incrementThenThrowThenIncrement+      _ <- retryOnSqliteBusy mempty rng incrementThenThrowThenIncrement        withVar countVar $ \count ->         count @?= 2      , testCase "retryOnException exponentially backs off" $ do-       logMsgsVar <- newVar ([] :: [(Priority, Text)])+       logMsgsVar <- newVar ([] :: [Log])         let maxDelay = 100        let baseDelay = 1        let maxRetryCount = 6        let logger = makeLogger logMsgsVar -       result <- tryJust isErrorBusy (retryOnException isErrorBusy logger maxDelay baseDelay maxRetryCount rng (throwIO errorBusy))+       result <- tryJust isErrorBusy (retryOnException isErrorBusy (cmapWithPrio LogSession logger) maxDelay baseDelay maxRetryCount rng (throwIO errorBusy))         case result of          Left _ -> do            withVar logMsgsVar $ \logMsgs ->-             if | ((_, lastLogMsg) : _) <- logMsgs ->-                  -- uses log messages to indirectly check backoff...-                  lastLogMsg @?= "Retries exhausted - base delay: 64, maximumDelay: 100, maxRetryCount: 0, exception: SQLite3 returned ErrorBusy while attempting to perform : "+             -- uses log messages to check backoff...+             if | (LogSession (Session.LogHieDbRetriesExhausted baseDelay maximumDelay maxRetryCount _) : _) <- logMsgs -> do+                  baseDelay @?= 64+                  maximumDelay @?= 100+                  maxRetryCount @?= 0                 | otherwise -> assertFailure "Expected more than 0 log messages"          Right _ -> assertFailure "Expected ErrorBusy exception"   ]
test/exe/Main.hs view
@@ -55,6 +55,8 @@                                                            flushMessages,                                                            getInterfaceFilesDir,                                                            getStoredKeys,+                                                           isReferenceReady,+                                                           referenceReady,                                                            standardizeQuotes,                                                            waitForAction,                                                            waitForGC,@@ -93,8 +95,9 @@ import           Test.QuickCheck -- import Test.QuickCheck.Instances () import           Control.Concurrent.Async-import           Control.Lens                             (to, (^.))+import           Control.Lens                             (to, (^.), (.~)) import           Control.Monad.Extra                      (whenJust)+import           Data.Function                            ((&)) import           Data.IORef import           Data.IORef.Extra                         (atomicModifyIORef_) import           Data.String                              (IsString (fromString))@@ -105,11 +108,23 @@ import           Development.IDE.Plugin.Test              (TestRequest (BlockSeconds),                                                            WaitForIdeRuleResult (..),                                                            blockCommandId)+import           Development.IDE.Types.Logger             (Logger (Logger),+                                                           LoggingColumn (DataColumn, PriorityColumn),+                                                           Pretty (pretty),+                                                           Priority (Debug),+                                                           Recorder (Recorder, logger_),+                                                           WithPriority (WithPriority, priority),+                                                           cfilter,+                                                           cmapWithPrio,+                                                           makeDefaultStderrRecorder)+import qualified FuzzySearch+import           GHC.Stack                                (emptyCallStack) import qualified HieDbRetry import           Ide.PluginUtils                          (pluginDescToIdePlugins) import           Ide.Types import qualified Language.LSP.Types                       as LSP import qualified Language.LSP.Types.Lens                  as L+import           Language.LSP.Types.Lens                  (workspace, didChangeWatchedFiles) import qualified Progress import           System.Time.Extra import           Test.Tasty@@ -120,6 +135,15 @@ import           Text.Printf                              (printf) import           Text.Regex.TDFA                          ((=~)) +data Log+  = LogGhcIde Ghcide.Log+  | LogIDEMain IDE.Log++instance Pretty Log where+  pretty = \case+    LogGhcIde log  -> pretty log+    LogIDEMain log -> pretty log+ -- | Wait for the next progress begin step waitForProgressBegin :: Session () waitForProgressBegin = skipManyTill anyMessage $ satisfyMaybe $ \case@@ -148,6 +172,18 @@  main :: IO () main = do+  docWithPriorityRecorder <- makeDefaultStderrRecorder (Just [PriorityColumn, DataColumn]) Debug++  let docWithFilteredPriorityRecorder@Recorder{ logger_ } =+        docWithPriorityRecorder+        & cfilter (\WithPriority{ priority } -> priority >= Debug)++  -- exists so old-style logging works. intended to be phased out+  let logger = Logger $ \p m -> logger_ (WithPriority p emptyCallStack (pretty m))++  let recorder = docWithFilteredPriorityRecorder+               & cmapWithPrio pretty+   -- We mess with env vars so run single-threaded.   defaultMainWithRerun $ testGroup "ghcide"     [ testSession "open close" $ do@@ -171,7 +207,7 @@     , thTests     , symlinkTests     , safeTests-    , unitTests+    , unitTests recorder logger     , haddockTests     , positionMappingTests     , watchedFilesTests@@ -386,9 +422,12 @@       let contentA = T.unlines [ "module ModuleA where" ]       _ <- createDoc "ModuleA.hs" "haskell" contentA       expectDiagnostics [("ModuleB.hs", [])]-  , ignoreInWindowsBecause "Broken in windows" $ testSessionWait "add missing module (non workspace)" $ do-      -- need to canonicalize in Mac Os-      tmpDir <- liftIO $ canonicalizePath =<< getTemporaryDirectory+  , testCase "add missing module (non workspace)" $+    -- By default lsp-test sends FileWatched notifications for all files, which we don't want+    -- as non workspace modules will not be watched by the LSP server.+    -- To work around this, we tell lsp-test that our client doesn't have the+    -- FileWatched capability, which is enough to disable the notifications+    withTempDir $ \tmpDir -> runInDir'' lspTestCapsNoFileWatches tmpDir "." "." [] $ do       let contentB = T.unlines             [ "module ModuleB where"             , "import ModuleA ()"@@ -1153,73 +1192,71 @@  typeWildCardActionTests :: TestTree typeWildCardActionTests = testGroup "type wildcard actions"-  [ testSession "global signature" $ do-      let content = T.unlines-            [ "module Testing where"-            , "func :: _"-            , "func x = x"-            ]-      doc <- createDoc "Testing.hs" "haskell" content-      _ <- waitForDiagnostics-      actionsOrCommands <- getAllCodeActions doc-      let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands-                                   , "Use type signature" `T.isInfixOf` actionTitle-                           ]-      executeCodeAction addSignature-      contentAfterAction <- documentContents doc-      let expectedContentAfterAction = T.unlines-            [ "module Testing where"-            , "func :: (p -> p)"-            , "func x = x"-            ]-      liftIO $ expectedContentAfterAction @=? contentAfterAction-  , testSession "multi-line message" $ do-      let content = T.unlines-            [ "module Testing where"-            , "func :: _"-            , "func x y = x + y"-            ]-      doc <- createDoc "Testing.hs" "haskell" content-      _ <- waitForDiagnostics-      actionsOrCommands <- getAllCodeActions doc-      let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands-                                    , "Use type signature" `T.isInfixOf` actionTitle-                              ]-      executeCodeAction addSignature-      contentAfterAction <- documentContents doc-      let expectedContentAfterAction = T.unlines-            [ "module Testing where"-            , "func :: (Integer -> Integer -> Integer)"-            , "func x y = x + y"-            ]-      liftIO $ expectedContentAfterAction @=? contentAfterAction-  , testSession "local signature" $ do-      let content = T.unlines-            [ "module Testing where"-            , "func :: Int -> Int"-            , "func x ="-            , "  let y :: _"-            , "      y = x * 2"-            , "  in y"-            ]-      doc <- createDoc "Testing.hs" "haskell" content-      _ <- waitForDiagnostics-      actionsOrCommands <- getAllCodeActions doc-      let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands-                                    , "Use type signature" `T.isInfixOf` actionTitle-                              ]-      executeCodeAction addSignature-      contentAfterAction <- documentContents doc-      let expectedContentAfterAction = T.unlines-            [ "module Testing where"-            , "func :: Int -> Int"-            , "func x ="-            , "  let y :: (Int)"-            , "      y = x * 2"-            , "  in y"-            ]-      liftIO $ expectedContentAfterAction @=? contentAfterAction+  [ testUseTypeSignature "global signature"+        [ "func :: _"+        , "func x = x"+        ]+        [ "func :: (p -> p)"+        , "func x = x"+        ]+  , testUseTypeSignature "local signature"+        [ "func :: Int -> Int"+        , "func x ="+        , "  let y :: _"+        , "      y = x * 2"+        , "  in y"+        ]+        [ "func :: Int -> Int"+        , "func x ="+        , "  let y :: Int"+        , "      y = x * 2"+        , "  in y"+        ]+  , testUseTypeSignature "multi-line message"+        [ "func :: _"+        , "func x y = x + y"+        ]+        [ "func :: (Integer -> Integer -> Integer)"+        , "func x y = x + y"+        ]+  , testUseTypeSignature "type in parentheses"+        [ "func :: a -> _"+        , "func x = (x, const x)"+        ]+        [ "func :: a -> (a, b -> a)"+        , "func x = (x, const x)"+        ]+  , testUseTypeSignature "type in brackets"+        [ "func :: _ -> Maybe a"+        , "func xs = head xs"+        ]+        [ "func :: [Maybe a] -> Maybe a"+        , "func xs = head xs"+        ]+  , testUseTypeSignature "unit type"+        [ "func :: IO _"+        , "func = putChar 'H'"+        ]+        [ "func :: IO ()"+        , "func = putChar 'H'"+        ]   ]+  where+    -- | Test session of given name, checking action "Use type signature..."+    --   on a test file with given content and comparing to expected result.+    testUseTypeSignature name textIn textOut = testSession name $ do+        let fileStart = "module Testing where"+            content = T.unlines $ fileStart : textIn+            expectedContentAfterAction = T.unlines $ fileStart : textOut+        doc <- createDoc "Testing.hs" "haskell" content+        _ <- waitForDiagnostics+        actionsOrCommands <- getAllCodeActions doc+        let [addSignature] = [action | InR action@CodeAction { _title = actionTitle } <- actionsOrCommands+                                    , "Use type signature" `T.isInfixOf` actionTitle+                            ]+        executeCodeAction addSignature+        contentAfterAction <- documentContents doc+        liftIO $ expectedContentAfterAction @=? contentAfterAction  {-# HLINT ignore "Use nubOrd" #-} removeImportTests :: TestTree@@ -1480,9 +1517,110 @@   ]   where     tests overrideCheckProject =-        [ testSession "extend single line import with value" $ template+        [ testSession "extend all constructors for record field" $ template             [("ModuleA.hs", T.unlines                     [ "module ModuleA where"+                    , "data A = B { a :: Int }"+                    ])]+            ("ModuleB.hs", T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA (A(B))"+                    , "f = a"+                    ])+            (Range (Position 2 4) (Position 2 5))+            [ "Add A(..) to the import list of ModuleA"+            , "Add A(a) to the import list of ModuleA"+            , "Add a to the import list of ModuleA"+            ]+            (T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA (A(..))"+                    , "f = a"+                    ])+        , testSession "extend all constructors with sibling" $ template+            [("ModuleA.hs", T.unlines+                    [ "module ModuleA where"+                    , "data Foo"+                    , "data Bar"+                    , "data A = B | C"+                    ])]+            ("ModuleB.hs", T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA ( Foo,  A (C) , Bar ) "+                    , "f = B"+                    ])+            (Range (Position 2 4) (Position 2 5))+            [ "Add A(..) to the import list of ModuleA"+            , "Add A(B) to the import list of ModuleA"+            ]+            (T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA ( Foo,  A (..) , Bar ) "+                    , "f = B"+                    ])+        , testSession "extend all constructors with comment" $ template+            [("ModuleA.hs", T.unlines+                    [ "module ModuleA where"+                    , "data Foo"+                    , "data Bar"+                    , "data A = B | C"+                    ])]+            ("ModuleB.hs", T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA ( Foo,  A (C{-comment--}) , Bar ) "+                    , "f = B"+                    ])+            (Range (Position 2 4) (Position 2 5))+            [ "Add A(..) to the import list of ModuleA"+            , "Add A(B) to the import list of ModuleA"+            ]+            (T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA ( Foo,  A (..{-comment--}) , Bar ) "+                    , "f = B"+                    ])+        , testSession "extend all constructors for type operator" $ template+            []+            ("ModuleA.hs", T.unlines+                    [ "module ModuleA where"+                    , "import Data.Type.Equality ((:~:))"+                    , "x :: (:~:) [] []"+                    , "x = Refl"+                    ])+            (Range (Position 3 17) (Position 3 18))+            [ "Add (:~:)(..) to the import list of Data.Type.Equality"+            , "Add type (:~:)(Refl) to the import list of Data.Type.Equality"]+            (T.unlines+                    [ "module ModuleA where"+                    , "import Data.Type.Equality ((:~:) (..))"+                    , "x :: (:~:) [] []"+                    , "x = Refl"+                    ])+        , testSession "extend all constructors for class" $ template+            [("ModuleA.hs", T.unlines+                    [ "module ModuleA where"+                    , "class C a where"+                    , "  m1 :: a -> a"+                    , "  m2 :: a -> a"+                    ])]+            ("ModuleB.hs", T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA (C(m1))"+                    , "b = m2"+                    ])+            (Range (Position 2 5) (Position 2 5))+            [ "Add C(..) to the import list of ModuleA"+            , "Add C(m2) to the import list of ModuleA"+            , "Add m2 to the import list of ModuleA"+            ]+            (T.unlines+                    [ "module ModuleB where"+                    , "import ModuleA (C(..))"+                    , "b = m2"+                    ])+        , testSession "extend single line import with value" $ template+            [("ModuleA.hs", T.unlines+                    [ "module ModuleA where"                     , "stuffA :: Double"                     , "stuffA = 0.00750"                     , "stuffB :: Integer"@@ -1520,6 +1658,40 @@                     , "import ModuleA as A (stuffB, (.*))"                     , "main = print (stuffB .* stuffB)"                     ])+        , testSession "extend single line import with infix constructor" $ template+            []+            ("ModuleB.hs", T.unlines+                    [ "module ModuleB where"+                    , "import Data.List.NonEmpty (fromList)"+                    , "main = case (fromList []) of _ :| _ -> pure ()"+                    ])+            (Range (Position 2 5) (Position 2 6))+            [ "Add NonEmpty((:|)) to the import list of Data.List.NonEmpty"+            , "Add NonEmpty(..) to the import list of Data.List.NonEmpty"+            ]+            (T.unlines+                    [ "module ModuleB where"+                    , "import Data.List.NonEmpty (fromList, NonEmpty ((:|)))"+                    , "main = case (fromList []) of _ :| _ -> pure ()"+                    ])+        , testSession "extend single line import with prefix constructor" $ template+            []+            ("ModuleB.hs", T.unlines+                    [ "module ModuleB where"+                    , "import Prelude hiding (Maybe(..))"+                    , "import Data.Maybe (catMaybes)"+                    , "x = Just 10"+                    ])+            (Range (Position 3 5) (Position 2 6))+            [ "Add Maybe(Just) to the import list of Data.Maybe"+            , "Add Maybe(..) to the import list of Data.Maybe"+            ]+            (T.unlines+                    [ "module ModuleB where"+                    , "import Prelude hiding (Maybe(..))"+                    , "import Data.Maybe (catMaybes, Maybe (Just))"+                    , "x = Just 10"+                    ])         , testSession "extend single line import with type" $ template             [("ModuleA.hs", T.unlines                     [ "module ModuleA where"@@ -1551,7 +1723,9 @@                     , "b = Constructor"                     ])             (Range (Position 3 5) (Position 3 5))-            ["Add A(Constructor) to the import list of ModuleA"]+            [ "Add A(Constructor) to the import list of ModuleA"+            , "Add A(..) to the import list of ModuleA"+            ]             (T.unlines                     [ "module ModuleB where"                     , "import ModuleA (A (Constructor))"@@ -1570,7 +1744,9 @@                     , "b = Constructor"                     ])             (Range (Position 3 5) (Position 3 5))-            ["Add A(Constructor) to the import list of ModuleA"]+            [ "Add A(Constructor) to the import list of ModuleA"+            , "Add A(..) to the import list of ModuleA"+            ]             (T.unlines                     [ "module ModuleB where"                     , "import ModuleA (A (Constructor{-Constructor-}))"@@ -1590,7 +1766,9 @@                     , "b = ConstructorFoo"                     ])             (Range (Position 3 5) (Position 3 5))-            ["Add A(ConstructorFoo) to the import list of ModuleA"]+            [ "Add A(ConstructorFoo) to the import list of ModuleA"+            , "Add A(..) to the import list of ModuleA"+            ]             (T.unlines                     [ "module ModuleB where"                     , "import ModuleA (A (ConstructorBar, ConstructorFoo), a)"@@ -1652,8 +1830,10 @@                     , "b = m2"                     ])             (Range (Position 2 5) (Position 2 5))-            ["Add C(m2) to the import list of ModuleA",-             "Add m2 to the import list of ModuleA"]+            [ "Add C(m2) to the import list of ModuleA"+            , "Add m2 to the import list of ModuleA"+            , "Add C(..) to the import list of ModuleA"+            ]             (T.unlines                     [ "module ModuleB where"                     , "import ModuleA (C(m1, m2))"@@ -1672,8 +1852,10 @@                     , "b = m2"                     ])             (Range (Position 2 5) (Position 2 5))-            ["Add m2 to the import list of ModuleA",-             "Add C(m2) to the import list of ModuleA"]+            [ "Add m2 to the import list of ModuleA"+            , "Add C(m2) to the import list of ModuleA"+            , "Add C(..) to the import list of ModuleA"+            ]             (T.unlines                     [ "module ModuleB where"                     , "import ModuleA (C(m1), m2)"@@ -1714,7 +1896,8 @@                     , "x = Refl"                     ])             (Range (Position 3 17) (Position 3 18))-            ["Add type (:~:)(Refl) to the import list of Data.Type.Equality"]+            [ "Add type (:~:)(Refl) to the import list of Data.Type.Equality"+            , "Add (:~:)(..) to the import list of Data.Type.Equality"]             (T.unlines                     [ "module ModuleA where"                     , "import Data.Type.Equality ((:~:) (Refl))"@@ -1754,7 +1937,7 @@                     , "f = Foo 1"                     ])             (Range (Position 3 4) (Position 3 6))-            ["Add Foo(Foo) to the import list of ModuleA"]+            ["Add Foo(Foo) to the import list of ModuleA", "Add Foo(..) to the import list of ModuleA"]             (T.unlines                     [ "module ModuleB where"                     , "import ModuleA(Foo (Foo))"@@ -1934,11 +2117,14 @@     , test False []         "f ExitSuccess = ()"          []                "import System.Exit (ExitSuccess)"       -- don't suggest data constructor when we only need the type     , test False []         "f :: Bar"                    []                "import Bar (Bar(Bar))"+      -- don't suggest all data constructors for the data type+    , test False []         "f :: Bar"                    []                "import Bar (Bar(..))"     ]   , testGroup "want suggestion"     [ wantWait  []          "f = foo"                     []                "import Foo (foo)"     , wantWait  []          "f = Bar"                     []                "import Bar (Bar(Bar))"     , wantWait  []          "f :: Bar"                    []                "import Bar (Bar)"+    , wantWait  []          "f = Bar"                     []                "import Bar (Bar(..))"     , test True []          "f = nonEmpty"                []                "import Data.List.NonEmpty (nonEmpty)"     , test True []          "f = (:|)"                    []                "import Data.List.NonEmpty (NonEmpty((:|)))"     , test True []          "f :: Natural"                ["f = undefined"] "import Numeric.Natural (Natural)"@@ -1980,12 +2166,15 @@       , "qualified Data.Functor as T"       , "qualified Data.Data as T"       ]                     "f = T.putStrLn"              []                "import qualified Data.Text.IO as T"+    , test True []          "f = (.|.)"                   []                "import Data.Bits (Bits(..))"+    , test True []          "f = empty"                   []                "import Control.Applicative (Alternative(..))"     ]-    , expectFailBecause "importing pattern synonyms is unsupported" $ test True [] "k (Some x) = x" [] "import B (pattern Some)"+  , expectFailBecause "importing pattern synonyms is unsupported" $ test True [] "k (Some x) = x" [] "import B (pattern Some)"   ]   where     test = test' False     wantWait = test' True True+     test' waitForCheckProject wanted imps def other newImp = testSessionWithExtraFiles "hover" (T.unpack def) $ \dir -> do       configureCheckProject waitForCheckProject       let before = T.unlines $ "module A where" : ["import " <> x | x <- imps] ++ def : other@@ -1995,7 +2184,7 @@       liftIO $ writeFileUTF8 (dir </> "B.hs") $ unlines ["{-# LANGUAGE PatternSynonyms #-}", "module B where", "pattern Some x = Just x"]       doc <- createDoc "Test.hs" "haskell" before       waitForProgressDone-      _diags <- waitForDiagnostics+      _ <- waitForDiagnostics       -- there isn't a good way to wait until the whole project is checked atm       when waitForCheckProject $ liftIO $ sleep 0.5       let defLine = fromIntegral $ length imps + 1@@ -2354,7 +2543,7 @@       liftIO $ contentAfterAction @?= T.unlines (txtB ++         [ ""         , "select :: [Bool] -> Bool"-        , "select = error \"not implemented\""+        , "select = _"         ]         ++ txtB')   , testSession "define a hole" $ do@@ -2381,9 +2570,61 @@         ,"foo False = False"         , ""         , "select :: [Bool] -> Bool"-        , "select = error \"not implemented\""+        , "select = _"         ]         ++ txtB')+  , testSession "insert new function definition - Haddock comments" $ do+    let start =  ["foo :: Int -> Bool"+                 , "foo x = select (x + 1)"+                 , ""+                 , "-- | This is a haddock comment"+                 , "haddock :: Int -> Int"+                 , "haddock = undefined"+                 ]+    let expected =  ["foo :: Int -> Bool"+             , "foo x = select (x + 1)"+             , ""+             , "select :: Int -> Bool"+             , "select = _"+             , ""+             , "-- | This is a haddock comment"+             , "haddock :: Int -> Int"+             , "haddock = undefined"]+    docB <- createDoc "ModuleB.hs" "haskell" (T.unlines start)+    _ <- waitForDiagnostics+    InR action@CodeAction { _title = actionTitle } : _+                <- sortOn (\(InR CodeAction{_title=x}) -> x) <$>+                    getCodeActions docB (R 1 0 0 50)+    liftIO $ actionTitle @?= "Define select :: Int -> Bool"+    executeCodeAction action+    contentAfterAction <- documentContents docB+    liftIO $ contentAfterAction @?= T.unlines expected+  , testSession "insert new function definition - normal comments" $ do+    let start =  ["foo :: Int -> Bool"+                 , "foo x = select (x + 1)"+                 , ""+                 , "-- This is a normal comment"+                 , "normal :: Int -> Int"+                 , "normal = undefined"+                 ]+    let expected =  ["foo :: Int -> Bool"+             , "foo x = select (x + 1)"+             , ""+             , "select :: Int -> Bool"+             , "select = _"+             , ""+             , "-- This is a normal comment"+             , "normal :: Int -> Int"+             , "normal = undefined"]+    docB <- createDoc "ModuleB.hs" "haskell" (T.unlines start)+    _ <- waitForDiagnostics+    InR action@CodeAction { _title = actionTitle } : _+                <- sortOn (\(InR CodeAction{_title=x}) -> x) <$>+                    getCodeActions docB (R 1 0 0 50)+    liftIO $ actionTitle @?= "Define select :: Int -> Bool"+    executeCodeAction action+    contentAfterAction <- documentContents docB+    liftIO $ contentAfterAction @?= T.unlines expected   ]  @@ -3942,7 +4183,9 @@     , testGroup "hover"      $ mapMaybe snd tests     , checkFileCompiles sourceFilePath $         expectDiagnostics-          [ ( "GotoHover.hs", [(DsError, (62, 7), "Found hole: _")]) ]+          [ ( "GotoHover.hs", [(DsError, (62, 7), "Found hole: _")])+          , ( "GotoHover.hs", [(DsError, (65, 8), "Found hole: _")])+          ]     , testGroup "type-definition" typeDefinitionTests ]    typeDefinitionTests = [ tst (getTypeDefinitions, checkDefs) aaaL14 (pure tcData) "Saturated data con"@@ -3994,6 +4237,7 @@   outL45 = Position 49  3  ;  outSig = [ExpectHoverText ["outer", "Bool"], mkR 50 0 50 5]   innL48 = Position 52  5  ;  innSig = [ExpectHoverText ["inner", "Char"], mkR 49 2 49 7]   holeL60 = Position 62 7  ;  hleInfo = [ExpectHoverText ["_ ::"]]+  holeL65 = Position 65 8  ;  hleInfo2 = [ExpectHoverText ["_ :: a -> Maybe a"]]   cccL17 = Position 17 16  ;  docLink = [ExpectHoverText ["[Documentation](file:///"]]   imported = Position 56 13 ; importedSig = getDocUri "Foo.hs" >>= \foo -> return [ExpectHoverText ["foo", "Foo", "Haddock"], mkL foo 5 0 5 3]   reexported = Position 55 14 ; reexportedSig = getDocUri "Bar.hs" >>= \bar -> return [ExpectHoverText ["Bar", "Bar", "Haddock"], mkL bar 3 0 3 14]@@ -4048,6 +4292,7 @@   , test  no     yes    outL45     outSig        "top-level signature              #767"   , test  broken broken innL48     innSig        "inner     signature              #767"   , test  no     yes    holeL60    hleInfo       "hole without internal name       #831"+  , test  no     yes    holeL65    hleInfo2      "hole with variable"   , test  no     skip   cccL17     docLink       "Haddock html links"   , testM yes    yes    imported   importedSig   "Imported symbol"   , testM yes    yes    reexported reexportedSig "Imported symbol (reexported)"@@ -4370,6 +4615,7 @@     , testGroup "package" packageCompletionTests     , testGroup "project" projectCompletionTests     , testGroup "other" otherCompletionTests+    , testGroup "doc" completionDocTests     ]  completionTest :: String -> [T.Text] -> Position -> [(T.Text, CompletionItemKind, T.Text, Bool, Bool, Maybe (List TextEdit))] -> TestTree@@ -4594,13 +4840,13 @@       "constructor"       ["{-# OPTIONS_GHC -Wall #-}", "module A where", "f = True"]       (Position 2 8)-      [ ("True", CiConstructor, "True ", True, True, Nothing)+      [ ("True", CiConstructor, "True", True, True, Nothing)       ],     completionTest       "type"       ["{-# OPTIONS_GHC -Wall #-}", "module A () where", "f :: Boo", "f = True"]       (Position 2 8)-      [ ("Bool", CiStruct, "Bool ", True, True, Nothing)+      [ ("Bool", CiStruct, "Bool", True, True, Nothing)       ],     completionTest       "qualified"@@ -4623,6 +4869,15 @@        ]        (Position 3 6)        [],+    testGroup "ordering"+      [completionTest "qualified has priority"+        ["module A where"+        ,"import qualified Data.ByteString as BS"+        ,"f = BS.read"+        ]+        (Position 2 10)+        [("readFile", CiFunction, "readFile ${1:FilePath}", True, True, Nothing)]+        ],     testGroup "auto import snippets"       [ completionCommandTest         "show imports not in list - simple"@@ -4766,7 +5021,7 @@       -- This should be sufficient to detect that we are in a       -- type context and only show the completion to the type.       (Position 3 11)-      [("Integer", CiStruct, "Integer ", True, True, Nothing)],+      [("Integer", CiStruct, "Integer", True, True, Nothing)],      testSession "duplicate record fields" $ do       void $@@ -4813,7 +5068,7 @@         _ <- waitForDiagnostics         compls <- getCompletions doc (Position 2 12)         let compls' =-              [T.drop 1 $ T.dropEnd 10 d+              [T.drop 1 $ T.dropEnd 3 d               | CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown d)), _label}                 <- compls               , _label == "fromList"@@ -4833,7 +5088,7 @@         _ <- waitForDiagnostics         compls <- getCompletions doc (Position 2 7)         let compls' =-              [T.drop 1 $ T.dropEnd 10 d+              [T.drop 1 $ T.dropEnd 3 d               | CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown d)), _label}                 <- compls               , _label == "Map"@@ -4917,7 +5172,7 @@             ]         compls <- getCompletions doc (Position 1 10)         let compls' =-              [T.drop 1 $ T.dropEnd 10 d+              [T.drop 1 $ T.dropEnd 3 d               | CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown d)), _label}                 <- compls               , _label == "anidentifier"@@ -4976,6 +5231,97 @@           item ^. L.label @?= "anidentifier"     ] +completionDocTests :: [TestTree]+completionDocTests =+  [ testSession "local define" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "foo = ()"+        , "bar = fo"+        ]+      let expected = "*Defined at line 2, column 1 in this module*\n"+      test doc (Position 2 8) "foo" Nothing [expected]+  , testSession "local empty doc" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "foo = ()"+        , "bar = fo"+        ]+      test doc (Position 2 8) "foo" Nothing ["*Defined at line 2, column 1 in this module*\n"]+  , brokenForGhc9 $ testSession "local single line doc without '\\n'" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "-- |docdoc"+        , "foo = ()"+        , "bar = fo"+        ]+      test doc (Position 3 8) "foo" Nothing ["*Defined at line 3, column 1 in this module*\n* * *\ndocdoc\n"]+  , brokenForGhc9 $ testSession "local multi line doc with '\\n'" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "-- | abcabc"+        , "--"+        , "foo = ()"+        , "bar = fo"+        ]+      test doc (Position 4 8) "foo" Nothing ["*Defined at line 4, column 1 in this module*\n* * *\n abcabc\n"]+  , brokenForGhc9 $ testSession "local multi line doc without '\\n'" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "-- |     abcabc"+        , "--"+        , "--def"+        , "foo = ()"+        , "bar = fo"+        ]+      test doc (Position 5 8) "foo" Nothing ["*Defined at line 5, column 1 in this module*\n* * *\n     abcabc\n\ndef\n"]+  , testSession "extern empty doc" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "foo = od"+        ]+      let expected = "*Imported from 'Prelude'*\n"+      test doc (Position 1 8) "odd" (Just $ T.length expected) [expected]+  , brokenForMacGhc9 $ brokenForWinGhc9 $ testSession "extern single line doc without '\\n'" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "foo = no"+        ]+      let expected = "*Imported from 'Prelude'*\n* * *\n\n\nBoolean \"not\"\n"+      test doc (Position 1 8) "not" (Just $ T.length expected) [expected]+  , brokenForMacGhc9 $ brokenForWinGhc9 $ testSession "extern mulit line doc" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "foo = i"+        ]+      let expected = "*Imported from 'Prelude'*\n* * *\n\n\nIdentity function. \n```haskell\nid x = x\n```\n"+      test doc (Position 1 7) "id" (Just $ T.length expected) [expected]+  , testSession "extern defined doc" $ do+      doc <- createDoc "A.hs" "haskell" $ T.unlines+        [ "module A where"+        , "foo = i"+        ]+      let expected = "*Imported from 'Prelude'*\n"+      test doc (Position 1 7) "id" (Just $ T.length expected) [expected]+  ]+  where+    brokenForGhc9 = knownBrokenFor (BrokenForGHC [GHC90, GHC92]) "Completion doc doesn't support ghc9"+    brokenForWinGhc9 = knownBrokenFor (BrokenSpecific Windows [GHC90, GHC92]) "Extern doc doesn't support Windows for ghc9.2"+    -- https://gitlab.haskell.org/ghc/ghc/-/issues/20903+    brokenForMacGhc9 = knownBrokenFor (BrokenSpecific MacOS [GHC90, GHC92]) "Extern doc doesn't support MacOS for ghc9"+    test doc pos label mn expected = do+      _ <- waitForDiagnostics+      compls <- getCompletions doc pos+      let compls' = [+            -- We ignore doc uris since it points to the local path which determined by specific machines+            case mn of+                Nothing -> txt+                Just n -> T.take n txt+            | CompletionItem {_documentation = Just (CompletionDocMarkup (MarkupContent MkMarkdown txt)), ..} <- compls+            , _label == label+            ]+      liftIO $ compls' @?= expected+ highlightTests :: TestTree highlightTests = testGroup "highlight"   [ testSessionWait "value" $ do@@ -5229,33 +5575,62 @@ xfail = flip expectFailBecause  ignoreInWindowsBecause :: String -> TestTree -> TestTree-ignoreInWindowsBecause-    | isWindows = ignoreTestBecause-    | otherwise = \_ x -> x+ignoreInWindowsBecause = ignoreFor (BrokenForOS Windows)  ignoreInWindowsForGHC88And810 :: TestTree -> TestTree-ignoreInWindowsForGHC88And810-    | ghcVersion `elem` [GHC88, GHC810] =-        ignoreInWindowsBecause "tests are unreliable in windows for ghc 8.8 and 8.10"-    | otherwise = id+ignoreInWindowsForGHC88And810 =+    ignoreFor (BrokenSpecific Windows [GHC88, GHC810]) "tests are unreliable in windows for ghc 8.8 and 8.10"  ignoreForGHC92 :: String -> TestTree -> TestTree-ignoreForGHC92 msg-    | ghcVersion == GHC92 = ignoreTestBecause msg-    | otherwise = id+ignoreForGHC92 = ignoreFor (BrokenForGHC [GHC92])  ignoreInWindowsForGHC88 :: TestTree -> TestTree-ignoreInWindowsForGHC88-    | ghcVersion == GHC88 =-        ignoreInWindowsBecause "tests are unreliable in windows for ghc 8.8"-    | otherwise = id+ignoreInWindowsForGHC88 =+    ignoreFor (BrokenSpecific Windows [GHC88]) "tests are unreliable in windows for ghc 8.8"  knownBrokenForGhcVersions :: [GhcVersion] -> String -> TestTree -> TestTree-knownBrokenForGhcVersions ghcVers-    | ghcVersion `elem` ghcVers = expectFailBecause-    | otherwise = \_ x -> x+knownBrokenForGhcVersions ghcVers = knownBrokenFor (BrokenForGHC ghcVers) +data BrokenOS = Linux | MacOS | Windows deriving (Show) +data IssueSolution = Broken | Ignore deriving (Show)++data BrokenTarget =+    BrokenSpecific BrokenOS [GhcVersion]+    -- ^Broken for `BrokenOS` with `GhcVersion`+    | BrokenForOS BrokenOS+    -- ^Broken for `BrokenOS`+    | BrokenForGHC [GhcVersion]+    -- ^Broken for `GhcVersion`+    deriving (Show)++-- | Ignore test for specific os and ghc with reason.+ignoreFor :: BrokenTarget -> String -> TestTree -> TestTree+ignoreFor = knownIssueFor Ignore++-- | Known broken for specific os and ghc with reason.+knownBrokenFor :: BrokenTarget -> String -> TestTree -> TestTree+knownBrokenFor = knownIssueFor Broken++-- | Deal with `IssueSolution` for specific OS and GHC.+knownIssueFor :: IssueSolution -> BrokenTarget -> String -> TestTree -> TestTree+knownIssueFor solution = go . \case+    BrokenSpecific bos vers -> isTargetOS bos && isTargetGhc vers+    BrokenForOS bos -> isTargetOS bos+    BrokenForGHC vers -> isTargetGhc vers+    where+        isTargetOS = \case+            Windows -> isWindows+            MacOS -> isMac+            Linux -> not isWindows && not isMac++        isTargetGhc = elem ghcVersion++        go True = case solution of+            Broken -> expectFailBecause+            Ignore -> ignoreTestBecause+        go False = \_ -> id+ data Expect   = ExpectRange Range -- Both gotoDef and hover should report this range   | ExpectLocation Location@@ -5334,7 +5709,7 @@     [testGroup "dependencies" [sessionDepsArePickedUp]     ,testGroup "ignore-fatal" [ignoreFatalWarning]     ,testGroup "loading" [loadCradleOnlyonce, retryFailedCradle]-    ,testGroup "multi"   [simpleMultiTest, simpleMultiTest2, simpleMultiDefTest]+    ,testGroup "multi"   [simpleMultiTest, simpleMultiTest2, simpleMultiTest3, simpleMultiDefTest]     ,testGroup "sub-directory"   [simpleSubDirectoryTest]     ] @@ -5454,12 +5829,10 @@ simpleMultiTest = testCase "simple-multi-test" $ withLongTimeout $ runWithExtraFiles "multi" $ \dir -> do     let aPath = dir </> "a/A.hs"         bPath = dir </> "b/B.hs"-    aSource <- liftIO $ readFileUtf8 aPath-    adoc <- createDoc aPath "haskell" aSource+    adoc <- openDoc aPath "haskell"+    bdoc <- openDoc bPath "haskell"     WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" adoc     liftIO $ assertBool "A should typecheck" ideResultSuccess-    bSource <- liftIO $ readFileUtf8 bPath-    bdoc <- createDoc bPath "haskell" bSource     WaitForIdeRuleResult {..} <- waitForAction "TypeCheck" bdoc     liftIO $ assertBool "B should typecheck" ideResultSuccess     locs <- getDefinitions bdoc (Position 2 7)@@ -5472,18 +5845,33 @@ simpleMultiTest2 = testCase "simple-multi-test2" $ runWithExtraFiles "multi" $ \dir -> do     let aPath = dir </> "a/A.hs"         bPath = dir </> "b/B.hs"-    bSource <- liftIO $ readFileUtf8 bPath-    bdoc <- createDoc bPath "haskell" bSource-    expectNoMoreDiagnostics 10-    aSource <- liftIO $ readFileUtf8 aPath-    (TextDocumentIdentifier adoc) <- createDoc aPath "haskell" aSource-    -- Need to have some delay here or the test fails-    expectNoMoreDiagnostics 10+    bdoc <- openDoc bPath "haskell"+    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" bdoc+    TextDocumentIdentifier auri <- openDoc aPath "haskell"+    skipManyTill anyMessage $ isReferenceReady aPath     locs <- getDefinitions bdoc (Position 2 7)-    let fooL = mkL adoc 2 0 2 3+    let fooL = mkL auri 2 0 2 3     checkDefs locs (pure [fooL])     expectNoMoreDiagnostics 0.5 +-- Now with 3 components+simpleMultiTest3 :: TestTree+simpleMultiTest3 =+  testCase "simple-multi-test3" $ runWithExtraFiles "multi" $ \dir -> do+    let aPath = dir </> "a/A.hs"+        bPath = dir </> "b/B.hs"+        cPath = dir </> "c/C.hs"+    bdoc <- openDoc bPath "haskell"+    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" bdoc+    TextDocumentIdentifier auri <- openDoc aPath "haskell"+    skipManyTill anyMessage $ isReferenceReady aPath+    cdoc <- openDoc cPath "haskell"+    WaitForIdeRuleResult {} <- waitForAction "TypeCheck" cdoc+    locs <- getDefinitions cdoc (Position 2 7)+    let fooL = mkL auri 2 0 2 3+    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@@ -5492,11 +5880,7 @@     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+      skipManyTill anyMessage $ isReferenceReady aPath       closeDoc adoc       pure adoc     bSource <- liftIO $ readFileUtf8 bPath@@ -5523,12 +5907,22 @@         -- Dirty the cache         liftIO $ runInDir dir $ do             cDoc <- createDoc cPath "haskell" cSource-            _ <- getHover cDoc $ Position 4 3-            ~() <- skipManyTill anyMessage $ satisfyMaybe $ \case-                FromServerMess (SCustomMethod "ghcide/reference/ready") (NotMess NotificationMessage{_params = fp}) -> do-                    A.Success fp' <- pure $ fromJSON fp-                    if equalFilePath fp' cPath then pure () else Nothing-                _ -> Nothing+            -- We send a hover request then wait for either the hover response or+            -- `ghcide/reference/ready` notification.+            -- Once we receive one of the above, we wait for the other that we+            -- haven't received yet.+            -- If we don't wait for the `ready` notification it is possible+            -- that the `getDefinitions` request/response in the outer ghcide+            -- session will find no definitions.+            let hoverParams = HoverParams cDoc (Position 4 3) Nothing+            hoverRequestId <- sendRequest STextDocumentHover hoverParams+            let parseReadyMessage = isReferenceReady cPath+            let parseHoverResponse = responseForId STextDocumentHover hoverRequestId+            hoverResponseOrReadyMessage <- skipManyTill anyMessage ((Left <$> parseHoverResponse) <|> (Right <$> parseReadyMessage))+            _ <- skipManyTill anyMessage $+              case hoverResponseOrReadyMessage of+                Left _  -> void parseReadyMessage+                Right _ -> void parseHoverResponse             closeDoc cDoc         cdoc <- createDoc cPath "haskell" cSource         locs <- getDefinitions cdoc (Position 7 4)@@ -5938,11 +6332,7 @@     loop :: [FilePath] -> Session ()     loop [] = pure ()     loop docs = do-      doc <- skipManyTill anyMessage $ satisfyMaybe $ \case-          FromServerMess (SCustomMethod "ghcide/reference/ready") (NotMess NotificationMessage{_params = fp}) -> do-            A.Success fp' <- pure $ fromJSON fp-            find (fp' ==) docs-          _ -> Nothing+      doc <- skipManyTill anyMessage $ referenceReady (`elem` docs)       loop (delete doc docs)   loop docs   f dir@@ -6041,7 +6431,18 @@  -- | Takes a directory as well as relative paths to where we should launch the executable as well as the session root. runInDir' :: FilePath -> FilePath -> FilePath -> [String] -> Session a -> IO a-runInDir' dir startExeIn startSessionIn extraOptions s = do+runInDir' = runInDir'' lspTestCaps++runInDir''+    :: ClientCapabilities+    -> FilePath+    -> FilePath+    -> FilePath+    -> [String]+    -> Session b+    -> IO b+runInDir'' lspCaps dir startExeIn startSessionIn extraOptions s = do+   ghcideExe <- locateGhcideExecutable   let startDir = dir </> startExeIn   let projDir = dir </> startSessionIn@@ -6061,10 +6462,11 @@   -- Only sets HOME if it wasn't already set.   setEnv "HOME" "/homeless-shelter" False   conf <- getConfigFromEnv-  runSessionWithConfig conf cmd lspTestCaps projDir $ do+  runSessionWithConfig conf cmd lspCaps projDir $ do       configureCheckProject False       s + getConfigFromEnv :: IO SessionConfig getConfigFromEnv = do   logColor <- fromMaybe True <$> checkEnv "LSP_TEST_LOG_COLOR"@@ -6082,6 +6484,9 @@ lspTestCaps :: ClientCapabilities lspTestCaps = fullCaps { _window = Just $ WindowClientCapabilities (Just True) Nothing Nothing } +lspTestCapsNoFileWatches :: ClientCapabilities+lspTestCapsNoFileWatches = lspTestCaps & workspace . Lens._Just . didChangeWatchedFiles .~ Nothing+ openTestDataDoc :: FilePath -> Session TextDocumentIdentifier openTestDataDoc path = do   source <- liftIO $ readFileUtf8 $ "test/data" </> path@@ -6116,8 +6521,8 @@ findCodeAction :: TextDocumentIdentifier -> Range -> T.Text -> Session CodeAction findCodeAction doc range t = head <$> findCodeActions doc range [t] -unitTests :: TestTree-unitTests = do+unitTests :: Recorder (WithPriority Log) -> Logger -> TestTree+unitTests recorder logger = do   testGroup "Unit"      [ testCase "empty file path does NOT work with the empty String literal" $          uriToFilePath' (fromNormalizedUri $ filePathToUri' "") @?= Just "."@@ -6152,14 +6557,14 @@         let plugins = pluginDescToIdePlugins $                 [ (defaultPluginDescriptor $ fromString $ show i)                     { pluginNotificationHandlers = mconcat-                        [ mkPluginNotificationHandler LSP.STextDocumentDidOpen $ \_ _ _ ->+                        [ mkPluginNotificationHandler LSP.STextDocumentDidOpen $ \_ _ _ _ ->                             liftIO $ atomicModifyIORef_ orderRef (i:)                         ]                     }                     | i <- [(1::Int)..20]-                ] ++ Ghcide.descriptors+                ] ++ Ghcide.descriptors (cmapWithPrio LogGhcIde recorder) -        testIde IDE.testing{IDE.argsHlsPlugins = plugins} $ do+        testIde recorder (IDE.testing (cmapWithPrio LogIDEMain recorder) logger){IDE.argsHlsPlugins = plugins} $ do             _ <- createDoc "haskell" "A.hs" "module A where"             waitForProgressDone             actualOrder <- liftIO $ readIORef orderRef@@ -6171,6 +6576,7 @@            let msg = printf "Timestamps do not have millisecond resolution: %dus" resolution_us            assertBool msg (resolution_us <= 1000)      , Progress.tests+     , FuzzySearch.tests      ]  garbageCollectionTests :: TestTree@@ -6257,16 +6663,14 @@     if t /= t' then return delay_us else findResolution_us (delay_us * 10)  -testIde :: IDE.Arguments -> Session a -> IO a-testIde = testIde' "."--testIde' :: FilePath -> IDE.Arguments -> Session a -> IO a-testIde' projDir arguments session = do+testIde :: Recorder (WithPriority Log) -> IDE.Arguments -> Session () -> IO ()+testIde recorder arguments session = do     config <- getConfigFromEnv     cwd <- getCurrentDirectory     (hInRead, hInWrite) <- createPipe     (hOutRead, hOutWrite) <- createPipe-    let server = IDE.defaultMain arguments+    let projDir = "."+    let server = IDE.defaultMain (cmapWithPrio LogIDEMain recorder) arguments             { IDE.argsHandleIn = pure hInRead             , IDE.argsHandleOut = pure hOutWrite             }
test/src/Development/IDE/Test.hs view
@@ -29,7 +29,14 @@   , getStoredKeys   , waitForCustomMessage   , waitForGC-  ,getBuildKeysBuilt,getBuildKeysVisited,getBuildKeysChanged,getBuildEdgesCount,configureCheckProject) where+  , getBuildKeysBuilt+  , getBuildKeysVisited+  , getBuildKeysChanged+  , getBuildEdgesCount+  , getRebuildsCount+  , configureCheckProject+  , isReferenceReady+  , referenceReady) where  import           Control.Applicative.Combinators import           Control.Lens                    hiding (List)@@ -58,6 +65,7 @@ import           System.Directory                (canonicalizePath) import           System.Time.Extra import           Test.Tasty.HUnit+import System.FilePath (equalFilePath)  requireDiagnosticM     :: (Foldable f, Show (f Diagnostic), HasCallStack)@@ -218,6 +226,9 @@ getBuildEdgesCount :: Session (Either ResponseError Int) getBuildEdgesCount = tryCallTestPlugin GetBuildEdgesCount +getRebuildsCount :: Session (Either ResponseError Int)+getRebuildsCount = tryCallTestPlugin GetRebuildsCount+ getInterfaceFilesDir :: TextDocumentIdentifier -> Session FilePath getInterfaceFilesDir TextDocumentIdentifier{_uri} = callTestPlugin (GetInterfaceFilesDir _uri) @@ -254,3 +265,16 @@     sendNotification SWorkspaceDidChangeConfiguration         (DidChangeConfigurationParams $ toJSON             def{checkProject = overrideCheckProject})++-- | Pattern match a message from ghcide indicating that a file has been indexed+isReferenceReady :: FilePath -> Session ()+isReferenceReady p = void $ referenceReady (equalFilePath p)++referenceReady :: (FilePath -> Bool) -> Session FilePath+referenceReady pred = satisfyMaybe $ \case+  FromServerMess (SCustomMethod "ghcide/reference/ready") (NotMess NotificationMessage{_params})+    | A.Success fp <- A.fromJSON _params+    , pred fp+    -> Just fp+  _ -> Nothing+