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 +2/−1
- bench/lib/Experiments.hs +26/−8
- exe/Main.hs +76/−17
- ghcide.cabal +14/−8
- session-loader/Development/IDE/Session.hs +186/−90
- src/Development/IDE.hs +3/−3
- src/Development/IDE/Core/Compile.hs +395/−113
- src/Development/IDE/Core/FileExists.hs +45/−39
- src/Development/IDE/Core/FileStore.hs +68/−101
- src/Development/IDE/Core/FileUtils.hs +31/−0
- src/Development/IDE/Core/OfInterest.hs +22/−7
- src/Development/IDE/Core/RuleTypes.hs +17/−6
- src/Development/IDE/Core/Rules.hs +206/−191
- src/Development/IDE/Core/Service.hs +31/−10
- src/Development/IDE/Core/Shake.hs +272/−216
- src/Development/IDE/Core/UseStale.hs +162/−162
- src/Development/IDE/GHC/CPP.hs +63/−63
- src/Development/IDE/GHC/Compat.hs +155/−1
- src/Development/IDE/GHC/Compat/CPP.hs +204/−204
- src/Development/IDE/GHC/Compat/Core.hs +8/−0
- src/Development/IDE/GHC/Compat/Outputable.hs +35/−24
- src/Development/IDE/GHC/Compat/Parser.hs +17/−2
- src/Development/IDE/GHC/ExactPrint.hs +15/−14
- src/Development/IDE/GHC/Orphans.hs +23/−12
- src/Development/IDE/GHC/Util.hs +19/−12
- src/Development/IDE/Import/DependencyInformation.hs +2/−4
- src/Development/IDE/LSP/LanguageServer.hs +45/−28
- src/Development/IDE/LSP/Notifications.hs +33/−20
- src/Development/IDE/LSP/Outline.hs +23/−29
- src/Development/IDE/LSP/Server.hs +7/−4
- src/Development/IDE/Main.hs +113/−69
- src/Development/IDE/Main/HeapStats.hs +75/−53
- src/Development/IDE/Plugin/CodeAction.hs +48/−30
- src/Development/IDE/Plugin/CodeAction/Args.hs +1/−1
- src/Development/IDE/Plugin/CodeAction/ExactPrint.hs +61/−17
- src/Development/IDE/Plugin/Completions.hs +24/−11
- src/Development/IDE/Plugin/Completions/Logic.hs +20/−23
- src/Development/IDE/Plugin/HLS.hs +21/−11
- src/Development/IDE/Plugin/HLS/GhcIde.hs +18/−5
- src/Development/IDE/Plugin/Test.hs +5/−0
- src/Development/IDE/Plugin/TypeLenses.hs +18/−7
- src/Development/IDE/Spans/AtPoint.hs +12/−11
- src/Development/IDE/Spans/Common.hs +31/−20
- src/Development/IDE/Spans/Documentation.hs +225/−224
- src/Development/IDE/Spans/LocalBindings.hs +140/−140
- src/Development/IDE/Spans/Pragmas.hs +11/−2
- src/Development/IDE/Types/Exports.hs +8/−9
- src/Development/IDE/Types/HscEnvEq.hs +153/−153
- src/Development/IDE/Types/Logger.hs +313/−10
- src/Development/IDE/Types/Options.hs +1/−11
- src/Development/IDE/Types/Shake.hs +3/−3
- src/Generics/SYB/GHC.hs +124/−124
- src/Text/Fuzzy/Parallel.hs +93/−138
- 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 +0/−50
- test/data/hover/GotoHover.hs +3/−0
- test/data/multi/a/A.hs +1/−1
- test/data/multi/a/a.cabal +1/−1
- test/data/multi/c/C.hs +3/−0
- test/data/multi/c/c.cabal +9/−0
- test/data/multi/cabal.project +1/−1
- test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.2/a-1.0.0/build/autogen/Paths_a.hs +0/−50
- test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/a-1.0.0/build/autogen/Paths_a.hs +0/−50
- test/data/multi/dist-newstyle/build/x86_64-osx/ghc-8.10.3/b-1.0.0/build/autogen/Paths_b.hs +0/−50
- test/data/multi/hie.yaml +2/−0
- test/exe/FuzzySearch.hs +132/−0
- test/exe/HieDbRetry.hs +27/−16
- test/exe/Main.hs +555/−151
- test/src/Development/IDE/Test.hs +25/−1
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+