hsdev 0.2.5.1 → 0.3.0.0
raw patch · 88 files changed
+11120/−10131 lines, 88 filesdep +direct-sqlitedep +haskell-namesdep +sqlite-simpledep ~basedep ~containersdep ~directoryPVP ok
version bump matches the API change (PVP)
Dependencies added: direct-sqlite, haskell-names, sqlite-simple, stm
Dependency ranges changed: base, containers, directory, haskell-src-exts, hdocs, hformat, text-region
API changes (from Hackage documentation)
- Control.Apply.Util: with :: a -> [a -> a] -> a
- Control.Concurrent.FiniteChan: dupChan :: Chan a -> IO (Chan a)
- Control.Concurrent.FiniteChan: instance GHC.Classes.Eq Control.Concurrent.FiniteChan.State
- Control.Concurrent.FiniteChan: instance GHC.Classes.Ord Control.Concurrent.FiniteChan.State
- Control.Concurrent.FiniteChan: instance GHC.Enum.Bounded Control.Concurrent.FiniteChan.State
- Control.Concurrent.FiniteChan: instance GHC.Enum.Enum Control.Concurrent.FiniteChan.State
- Control.Concurrent.FiniteChan: instance GHC.Read.Read Control.Concurrent.FiniteChan.State
- Control.Concurrent.FiniteChan: instance GHC.Show.Show Control.Concurrent.FiniteChan.State
- Control.Concurrent.FiniteChan: putChan :: Chan a -> a -> IO ()
- Control.Concurrent.FiniteChan: readChan :: Chan a -> IO [a]
- Control.Concurrent.Worker: [workerTouch] :: Worker m -> IO ()
- Control.Concurrent.Worker: [workerWrap] :: Worker m -> forall a. m a -> m a
- Control.Concurrent.Worker: inWorker_ :: (MonadIO m, MonadCatch m) => Worker m -> m a -> ExceptT SomeException IO a
- Control.Concurrent.Worker: pushTask :: (MonadCatch m, MonadIO m) => Worker m -> m a -> IO (Async a)
- Control.Concurrent.Worker: restartWorker :: Worker m -> IO ()
- Data.Async: Action :: (a -> IO a) -> Event a
- Data.Async: Append :: a -> Event a
- Data.Async: Async :: MVar a -> Chan (Event a) -> Async a
- Data.Async: Clear :: Event a
- Data.Async: Modify :: (a -> a) -> Event a
- Data.Async: Remove :: a -> Event a
- Data.Async: [asyncEvents] :: Async a -> Chan (Event a)
- Data.Async: [asyncVar] :: Async a -> MVar a
- Data.Async: class Eq a => Group a
- Data.Async: class NFData a
- Data.Async: data Async a
- Data.Async: data Event a
- Data.Async: event :: Group a => Event a -> a -> IO a
- Data.Async: modifyAsync :: Async a -> Event a -> IO ()
- Data.Async: newAsync :: (NFData a, Group a) => IO (Async a)
- Data.Async: readAsync :: Async a -> IO a
- Data.Deps: data Deps a
- Data.Group: add :: Group a => a -> a -> a
- Data.Group: class Eq a => Group a
- Data.Group: groupSum :: Group a => [a] -> a
- Data.Group: instance (GHC.Classes.Ord k, Data.Group.Group a) => Data.Group.Group (Data.Map.Base.Map k a)
- Data.Group: instance GHC.Classes.Eq a => Data.Group.Group [a]
- Data.Group: instance GHC.Classes.Ord a => Data.Group.Group (Data.Set.Base.Set a)
- Data.Group: sub :: Group a => a -> a -> a
- Data.Group: zero :: Group a => a
- Data.Maybe.JustIf: justWhen :: a -> (a -> Bool) -> Maybe a
- Data.Maybe.JustIf: soJust :: Bool -> a -> Maybe a
- HsDev.Cache: data Database
- HsDev.Cache: dump :: FilePath -> Database -> IO ()
- HsDev.Cache: escapePath :: FilePath -> FilePath
- HsDev.Cache: load :: FilePath -> IO (Either String Database)
- HsDev.Cache: packageDbCache :: PackageDb -> FilePath
- HsDev.Cache: projectCache :: Project -> FilePath
- HsDev.Cache: readVersion :: FilePath -> IO (Maybe [Int])
- HsDev.Cache: standaloneCache :: FilePath
- HsDev.Cache: versionCache :: FilePath
- HsDev.Cache: writeVersion :: FilePath -> IO ()
- HsDev.Cache.Structured: dump :: FilePath -> Structured -> IO ()
- HsDev.Cache.Structured: load :: FilePath -> IO (Either String Structured)
- HsDev.Cache.Structured: loadFiles :: (FilePath -> Bool) -> FilePath -> ExceptT String IO Structured
- HsDev.Cache.Structured: loadPackageDb :: PackageDb -> FilePath -> ExceptT String IO Structured
- HsDev.Cache.Structured: loadProject :: FilePath -> FilePath -> ExceptT String IO Structured
- HsDev.Commands: checkDeclaration :: (Declaration -> Bool) -> (ModuleDeclaration -> Bool)
- HsDev.Commands: checkModule :: (ModuleId -> Bool) -> (ModuleDeclaration -> Bool)
- HsDev.Commands: completions :: Database -> FilePath -> String -> Bool -> ExceptT String IO [Declaration]
- HsDev.Commands: fileCtx :: Database -> FilePath -> ExceptT String IO (FilePath, Module, Maybe Project)
- HsDev.Commands: fileCtxMaybe :: Database -> FilePath -> ExceptT String IO (FilePath, Maybe Module, Maybe Project)
- HsDev.Commands: fileModule :: Database -> FilePath -> ExceptT String IO Module
- HsDev.Commands: findDeclaration :: Database -> String -> ExceptT String IO [ModuleDeclaration]
- HsDev.Commands: findModule :: Database -> String -> ExceptT String IO [Module]
- HsDev.Commands: lookupSymbol :: Database -> FilePath -> String -> ExceptT String IO [ModuleDeclaration]
- HsDev.Commands: moduleCompletions :: Database -> [Module] -> String -> ExceptT String IO [String]
- HsDev.Commands: restrictPackageDb :: PackageDb -> ModuleId -> Bool
- HsDev.Commands: restrictPackageDbStack :: PackageDbStack -> ModuleId -> Bool
- HsDev.Commands: scope :: Database -> FilePath -> Bool -> ExceptT String IO [Declaration]
- HsDev.Commands: scopeModules :: Database -> FilePath -> ExceptT String IO [Module]
- HsDev.Commands: splitIdentifier :: String -> (Maybe String, String)
- HsDev.Commands: visibleFrom :: Maybe Project -> Module -> ModuleId -> Bool
- HsDev.Commands: whois :: Database -> FilePath -> String -> ExceptT String IO [Declaration]
- HsDev.Database: Database :: [InspectedModule] -> [Project] -> Database
- HsDev.Database: Structured :: Map PackageDb Database -> Map FilePath Database -> Database -> Structured
- HsDev.Database: [databaseModules] :: Database -> [InspectedModule]
- HsDev.Database: [databaseProjects] :: Database -> [Project]
- HsDev.Database: [structuredFiles] :: Structured -> Database
- HsDev.Database: [structuredPackageDbs] :: Structured -> Map PackageDb Database
- HsDev.Database: [structuredProjects] :: Structured -> Map FilePath Database
- HsDev.Database: allDeclarations :: Database -> [ModuleDeclaration]
- HsDev.Database: allModules :: Database -> [Module]
- HsDev.Database: allPackages :: Database -> [ModulePackage]
- HsDev.Database: append :: Database -> Database -> Database
- HsDev.Database: data Database
- HsDev.Database: data Map k a :: * -> * -> *
- HsDev.Database: data Structured
- HsDev.Database: databaseIntersection :: Database -> Database -> Database
- HsDev.Database: databaseLocals :: Database -> Database
- HsDev.Database: databasePackageDbs :: Database -> [PackageDb]
- HsDev.Database: filterDB :: (ModuleId -> Bool) -> (Project -> Bool) -> Database -> Database
- HsDev.Database: fromModule :: InspectedModule -> Database
- HsDev.Database: fromProject :: Project -> Database
- HsDev.Database: getInspected :: Database -> Module -> InspectedModule
- HsDev.Database: instance Control.DeepSeq.NFData HsDev.Database.Database
- HsDev.Database: instance Control.DeepSeq.NFData HsDev.Database.Structured
- HsDev.Database: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Database.Database
- HsDev.Database: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Database.Structured
- HsDev.Database: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Database.Database
- HsDev.Database: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Database.Structured
- HsDev.Database: instance Data.Group.Group HsDev.Database.Database
- HsDev.Database: instance Data.Group.Group HsDev.Database.Structured
- HsDev.Database: instance GHC.Base.Monoid HsDev.Database.Database
- HsDev.Database: instance GHC.Base.Monoid HsDev.Database.Structured
- HsDev.Database: instance GHC.Classes.Eq HsDev.Database.Database
- HsDev.Database: instance GHC.Classes.Eq HsDev.Database.Structured
- HsDev.Database: instance GHC.Classes.Ord HsDev.Database.Database
- HsDev.Database: instance GHC.Classes.Ord HsDev.Database.Structured
- HsDev.Database: lookupFile :: FilePath -> Database -> Maybe Module
- HsDev.Database: lookupInspected :: ModuleLocation -> Database -> Maybe InspectedModule
- HsDev.Database: lookupModule :: ModuleLocation -> Database -> Maybe Module
- HsDev.Database: merge :: Structured -> Database
- HsDev.Database: nullDatabase :: Database -> Bool
- HsDev.Database: packageDbDB :: PackageDb -> Database -> Database
- HsDev.Database: packageDbStackDB :: PackageDbStack -> Database -> Database
- HsDev.Database: projectDB :: Project -> Database -> Database
- HsDev.Database: refineProject :: Database -> Project -> Maybe Project
- HsDev.Database: remove :: Database -> Database -> Database
- HsDev.Database: selectDeclarations :: (ModuleDeclaration -> Bool) -> Database -> [ModuleDeclaration]
- HsDev.Database: selectModules :: (Module -> Bool) -> Database -> [Module]
- HsDev.Database: standaloneDB :: Database -> Database
- HsDev.Database: structured :: [Database] -> [Database] -> Database -> Either String Structured
- HsDev.Database: structurize :: Database -> Structured
- HsDev.Database.Async: clear :: MonadIO m => Async Database -> m Database -> m ()
- HsDev.Database.Async: update :: MonadIO m => Async Database -> m Database -> m ()
- HsDev.Database.Async: wait :: MonadIO m => Async Database -> m ()
- HsDev.Database.Update: getCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> (Database -> Database) -> m Database
- HsDev.Database.Update: isStatus :: Value -> Bool
- HsDev.Database.Update: loadCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> m Database
- HsDev.Database.Update: processEvent :: UpdateOptions -> Watched -> Event -> ClientM IO ()
- HsDev.Database.Update: readDB :: SessionMonad m => m Database
- HsDev.Database.Update: scanModule :: UpdateMonad m => [String] -> ModuleLocation -> Maybe String -> m ()
- HsDev.Database.Update: updateEvent :: ServerMonadBase m => Watched -> Event -> UpdateM m ()
- HsDev.Database.Update: waiter :: UpdateMonad m => m () -> m ()
- HsDev.Database.Update.Types: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader HsDev.Database.Update.Types.UpdateOptions (HsDev.Database.Update.Types.UpdateM m)
- HsDev.Display: instance HsDev.Display.Display HsDev.PackageDb.PackageDb
- HsDev.Display: instance Text.Format.Formattable HsDev.PackageDb.PackageDb
- HsDev.Error: hsdevExcept :: MonadCatch m => m a -> ExceptT HsDevError m a
- HsDev.Error: hsdevLog :: MonadLog m => Level -> m a -> m a
- HsDev.Inspect: analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module
- HsDev.Inspect: inspectDocsChunk :: [String] -> [Module] -> IO [Module]
- HsDev.Inspect: inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule])
- HsDev.PackageDb: GlobalDb :: PackageDb
- HsDev.PackageDb: PackageDb :: FilePath -> PackageDb
- HsDev.PackageDb: PackageDbStack :: [PackageDb] -> PackageDbStack
- HsDev.PackageDb: UserDb :: PackageDb
- HsDev.PackageDb: [_packageDbStack] :: PackageDbStack -> [PackageDb]
- HsDev.PackageDb: [_packageDb] :: PackageDb -> FilePath
- HsDev.PackageDb: data PackageDb
- HsDev.PackageDb: fromPackageDb :: FilePath -> PackageDbStack
- HsDev.PackageDb: fromPackageDbs :: [FilePath] -> PackageDbStack
- HsDev.PackageDb: globalDb :: PackageDbStack
- HsDev.PackageDb: instance Control.DeepSeq.NFData HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance Control.DeepSeq.NFData HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance GHC.Classes.Eq HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance GHC.Classes.Eq HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance GHC.Classes.Ord HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance GHC.Classes.Ord HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance GHC.Read.Read HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance GHC.Read.Read HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance GHC.Show.Show HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance GHC.Show.Show HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: instance System.Directory.Paths.Paths HsDev.PackageDb.PackageDb
- HsDev.PackageDb: instance System.Directory.Paths.Paths HsDev.PackageDb.PackageDbStack
- HsDev.PackageDb: isSubStack :: PackageDbStack -> PackageDbStack -> Bool
- HsDev.PackageDb: newtype PackageDbStack
- HsDev.PackageDb: packageDb :: Traversal' PackageDb FilePath
- HsDev.PackageDb: packageDbOpt :: PackageDb -> String
- HsDev.PackageDb: packageDbStack :: Iso' PackageDbStack [PackageDb]
- HsDev.PackageDb: packageDbStackOpts :: PackageDbStack -> [String]
- HsDev.PackageDb: packageDbStacks :: PackageDbStack -> [PackageDbStack]
- HsDev.PackageDb: packageDbs :: PackageDbStack -> [PackageDb]
- HsDev.PackageDb: topPackageDb :: PackageDbStack -> PackageDb
- HsDev.PackageDb: userDb :: PackageDbStack
- HsDev.Project: inTarget :: FilePath -> Info -> Bool
- HsDev.Project: infos :: ProjectDescription -> [Info]
- HsDev.Project.Types: absolutiseProjectPaths :: Project -> Project
- HsDev.Project.Types: relativiseProjectPaths :: Project -> Project
- HsDev.Scan: changedModule :: Database -> [String] -> ModuleLocation -> IO Bool
- HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.PackageDb.PackageDbStack
- HsDev.Scan: rescanModule :: CommandMonad m => [(String, String)] -> [String] -> InspectedModule -> m (Maybe InspectedModule)
- HsDev.Scan: scanModule :: CommandMonad m => [(String, String)] -> [String] -> ModuleLocation -> Maybe String -> m InspectedModule
- HsDev.Scan.Browse: browse :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule]
- HsDev.Scan.Browse: browseDb :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule]
- HsDev.Scan.Browse: ghcPackageDb :: PackageConfig -> IO PackageDb
- HsDev.Scan.Browse: packageDbCandidate :: FilePath -> IO (Maybe PackageDb)
- HsDev.Scan.Browse: packageDbCandidate_ :: FilePath -> IO PackageDb
- HsDev.Scan.Browse: withPackages :: MonadLog m => [String] -> (DynFlags -> GhcM a) -> m a
- HsDev.Scan.Browse: withPackages_ :: MonadLog m => [String] -> GhcM a -> m a
- HsDev.Server.Base: readCache :: SessionMonad m => ServerOpts -> (FilePath -> ExceptT String IO Structured) -> m (Maybe Database)
- HsDev.Server.Base: withCache :: Monad m => ServerOpts -> a -> (FilePath -> m a) -> m a
- HsDev.Server.Base: writeCache :: SessionMonad m => ServerOpts -> Database -> m ()
- HsDev.Server.Commands: [serverCache] :: ServerOpts -> Maybe FilePath
- HsDev.Server.Commands: [serverLoad] :: ServerOpts -> Bool
- HsDev.Server.Message: notification :: ToJSON a => a -> Response
- HsDev.Server.Message: responsesById :: Maybe String -> [Message Response] -> [([Notification], Result)]
- HsDev.Server.Message: resultPart :: ToJSON a => a -> Notification
- HsDev.Server.Types: AddData :: [AddedContents] -> Command
- HsDev.Server.Types: AddedDatabase :: Database -> AddedContents
- HsDev.Server.Types: AddedModule :: InspectedModule -> AddedContents
- HsDev.Server.Types: AddedProject :: Project -> AddedContents
- HsDev.Server.Types: AutoFixFix :: [Note Correction] -> [Note Correction] -> Bool -> AutoFixCommand
- HsDev.Server.Types: AutoFixShow :: [Note OutputMessage] -> AutoFixCommand
- HsDev.Server.Types: InfoModules :: [TargetFilter] -> Command
- HsDev.Server.Types: InfoResolve :: FilePath -> Bool -> Command
- HsDev.Server.Types: SearchRegex :: SearchType
- HsDev.Server.Types: TargetCabal :: TargetFilter
- HsDev.Server.Types: TargetDepsOf :: String -> TargetFilter
- HsDev.Server.Types: TargetPackageDb :: PackageDb -> TargetFilter
- HsDev.Server.Types: TargetSandbox :: FilePath -> TargetFilter
- HsDev.Server.Types: [addedContents] :: Command -> [AddedContents]
- HsDev.Server.Types: [autoFixCommand] :: Command -> AutoFixCommand
- HsDev.Server.Types: [docsModules] :: Command -> [String]
- HsDev.Server.Types: [inferModules] :: Command -> [String]
- HsDev.Server.Types: [serverCache] :: ServerOpts -> Maybe FilePath
- HsDev.Server.Types: [serverLoad] :: ServerOpts -> Bool
- HsDev.Server.Types: [sessionDatabase] :: Session -> Async Database
- HsDev.Server.Types: [sessionReadCache] :: Session -> (FilePath -> ExceptT String IO Structured) -> ServerM IO (Maybe Database)
- HsDev.Server.Types: [sessionWriteCache] :: Session -> Database -> ServerM IO ()
- HsDev.Server.Types: data AddedContents
- HsDev.Server.Types: data AutoFixCommand
- HsDev.Server.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Server.Types.AddedContents
- HsDev.Server.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Server.Types.AutoFixCommand
- HsDev.Server.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Server.Types.AddedContents
- HsDev.Server.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Server.Types.AutoFixCommand
- HsDev.Server.Types: instance GHC.Show.Show HsDev.Server.Types.AddedContents
- HsDev.Server.Types: instance GHC.Show.Show HsDev.Server.Types.AutoFixCommand
- HsDev.Server.Types: instance HsDev.Util.FromCmd HsDev.Server.Types.AutoFixCommand
- HsDev.Server.Types: instance System.Directory.Paths.Paths [HsDev.Server.Types.TargetFilter]
- HsDev.Server.Types: serverReadCache :: SessionMonad m => (FilePath -> ExceptT String IO Structured) -> m (Maybe Database)
- HsDev.Server.Types: serverUpdateDB :: SessionMonad m => Database -> m ()
- HsDev.Server.Types: serverWriteCache :: SessionMonad m => Database -> m ()
- HsDev.Symbols: addDeclaration :: Declaration -> Module -> Module
- HsDev.Symbols: class Locals a
- HsDev.Symbols: class Symbol a
- HsDev.Symbols: clearDefinedIn :: Module -> Module
- HsDev.Symbols: decl :: Text -> DeclarationInfo -> Declaration
- HsDev.Symbols: declarationLocals :: Declaration -> [Declaration]
- HsDev.Symbols: definedIn :: Declaration -> ModuleId -> Declaration
- HsDev.Symbols: dropExternals :: Module -> Module
- HsDev.Symbols: export :: Export -> Text
- HsDev.Symbols: exported :: Declaration -> Export -> Bool
- HsDev.Symbols: importNames :: Import -> [Text]
- HsDev.Symbols: importQualifier :: Maybe Text -> Import -> Bool
- HsDev.Symbols: import_ :: Text -> Import
- HsDev.Symbols: imported :: Declaration -> ImportList -> Bool
- HsDev.Symbols: instance HsDev.Symbols.Locals HsDev.Symbols.Types.Declaration
- HsDev.Symbols: instance HsDev.Symbols.Locals HsDev.Symbols.Types.DeclarationInfo
- HsDev.Symbols: locals :: Locals a => a -> [Declaration]
- HsDev.Symbols: mergeExported :: [ModuleDeclaration] -> [ExportedDeclaration]
- HsDev.Symbols: moduleLocalDeclarations :: Module -> [Declaration]
- HsDev.Symbols: moduleLocals :: Module -> Module
- HsDev.Symbols: moduleModuleDeclarations :: Module -> [ModuleDeclaration]
- HsDev.Symbols: passImportSpec :: Declaration -> ImportSpec -> Bool
- HsDev.Symbols: passThingSpec :: Text -> ThingPart -> Bool
- HsDev.Symbols: scopes :: Declaration -> [Maybe Text]
- HsDev.Symbols: setDefinedIn :: Module -> Module
- HsDev.Symbols: sortDeclarations :: [Declaration] -> [Declaration]
- HsDev.Symbols: symbolDocs :: Symbol a => a -> Maybe Text
- HsDev.Symbols: symbolLocation :: Symbol a => a -> Location
- HsDev.Symbols: symbolName :: Symbol a => a -> Text
- HsDev.Symbols: symbolQualifiedName :: Symbol a => a -> Text
- HsDev.Symbols: unalias :: Module -> Text -> [Text]
- HsDev.Symbols: unnamedModuleId :: ModuleLocation -> ModuleId
- HsDev.Symbols: where_ :: Locals a => a -> [Declaration] -> a
- HsDev.Symbols.Class: class Symbol a
- HsDev.Symbols.Class: symbolDocs :: Symbol a => a -> Maybe Text
- HsDev.Symbols.Class: symbolLocation :: Symbol a => a -> Location
- HsDev.Symbols.Class: symbolModuleLocation :: Symbol a => a -> ModuleLocation
- HsDev.Symbols.Class: symbolName :: Symbol a => a -> Text
- HsDev.Symbols.Class: symbolQualifiedName :: Symbol a => a -> Text
- HsDev.Symbols.Location: ModuleSource :: Maybe String -> ModuleLocation
- HsDev.Symbols.Location: [_cabalModuleName] :: ModuleLocation -> String
- HsDev.Symbols.Location: [_modulePackageDb] :: ModuleLocation -> PackageDb
- HsDev.Symbols.Location: [_moduleSourceName] :: ModuleLocation -> Maybe String
- HsDev.Symbols.Location: cabalModuleName :: Traversal' ModuleLocation String
- HsDev.Symbols.Location: importedModulePath :: Text -> FilePath -> Text -> FilePath
- HsDev.Symbols.Location: modulePackageDb :: Traversal' ModuleLocation PackageDb
- HsDev.Symbols.Location: moduleSourceName :: Traversal' ModuleLocation (Maybe String)
- HsDev.Symbols.Location: moduleStandalone :: ModuleLocation -> Bool
- HsDev.Symbols.Location: packageOpt :: Maybe ModulePackage -> [String]
- HsDev.Symbols.Resolve: ResolveM :: ReaderT (Database, ModuleMap) (State ResolvedTree) a -> ResolveM a
- HsDev.Symbols.Resolve: ResolvedModule :: Module -> [Declaration] -> [Declaration] -> ResolvedModule
- HsDev.Symbols.Resolve: [_resolvedExports] :: ResolvedModule -> [Declaration]
- HsDev.Symbols.Resolve: [_resolvedModule] :: ResolvedModule -> Module
- HsDev.Symbols.Resolve: [_resolvedScope] :: ResolvedModule -> [Declaration]
- HsDev.Symbols.Resolve: [runResolveM] :: ResolveM a -> ReaderT (Database, ModuleMap) (State ResolvedTree) a
- HsDev.Symbols.Resolve: data ResolvedModule
- HsDev.Symbols.Resolve: exportsModule :: ResolvedModule -> Module
- HsDev.Symbols.Resolve: instance Control.Monad.Reader.Class.MonadReader (HsDev.Database.Database, HsDev.Symbols.Resolve.ModuleMap) HsDev.Symbols.Resolve.ResolveM
- HsDev.Symbols.Resolve: instance Control.Monad.State.Class.MonadState HsDev.Symbols.Resolve.ResolvedTree HsDev.Symbols.Resolve.ResolveM
- HsDev.Symbols.Resolve: instance GHC.Base.Applicative HsDev.Symbols.Resolve.ResolveM
- HsDev.Symbols.Resolve: instance GHC.Base.Functor HsDev.Symbols.Resolve.ResolveM
- HsDev.Symbols.Resolve: instance GHC.Base.Monad HsDev.Symbols.Resolve.ResolveM
- HsDev.Symbols.Resolve: mergeImported :: [Declaration] -> [Declaration]
- HsDev.Symbols.Resolve: newtype ResolveM a
- HsDev.Symbols.Resolve: resolve :: Traversable t => Database -> t Module -> t ResolvedModule
- HsDev.Symbols.Resolve: resolveImport :: Set String -> Module -> Import -> ResolveM [Declaration]
- HsDev.Symbols.Resolve: resolveImports :: Module -> [Import] -> ResolveM [Declaration]
- HsDev.Symbols.Resolve: resolveModule :: Module -> ResolveM ResolvedModule
- HsDev.Symbols.Resolve: resolveOne :: Database -> Module -> ResolvedModule
- HsDev.Symbols.Resolve: resolvedExports :: Lens' ResolvedModule [Declaration]
- HsDev.Symbols.Resolve: resolvedModule :: Lens' ResolvedModule Module
- HsDev.Symbols.Resolve: resolvedScope :: Lens' ResolvedModule [Declaration]
- HsDev.Symbols.Resolve: resolvedTopScope :: ResolvedModule -> [Declaration]
- HsDev.Symbols.Resolve: scopeModule :: ResolvedModule -> Module
- HsDev.Symbols.Resolve: type ResolvedTree = Map ModuleId ResolvedModule
- HsDev.Symbols.Types: Declaration :: Text -> Maybe ModuleId -> Maybe [Import] -> Maybe Text -> Maybe Position -> DeclarationInfo -> Declaration
- HsDev.Symbols.Types: ExportModule :: Text -> Export
- HsDev.Symbols.Types: ExportName :: Maybe Text -> Text -> ThingPart -> Export
- HsDev.Symbols.Types: ExportedDeclaration :: [ModuleId] -> Declaration -> ExportedDeclaration
- HsDev.Symbols.Types: Import :: Text -> Bool -> Maybe Text -> Maybe ImportList -> Maybe Position -> Import
- HsDev.Symbols.Types: ImportList :: Bool -> [ImportSpec] -> ImportList
- HsDev.Symbols.Types: ImportSpec :: Text -> ThingPart -> ImportSpec
- HsDev.Symbols.Types: ModuleDeclaration :: ModuleId -> Declaration -> ModuleDeclaration
- HsDev.Symbols.Types: ModuleId :: Text -> ModuleLocation -> ModuleId
- HsDev.Symbols.Types: ThingAll :: ThingPart
- HsDev.Symbols.Types: ThingNothing :: ThingPart
- HsDev.Symbols.Types: ThingWith :: [Text] -> ThingPart
- HsDev.Symbols.Types: TypeInfo :: Maybe Text -> [Text] -> Maybe Text -> [Text] -> TypeInfo
- HsDev.Symbols.Types: [_declarationDefined] :: Declaration -> Maybe ModuleId
- HsDev.Symbols.Types: [_declarationDocs] :: Declaration -> Maybe Text
- HsDev.Symbols.Types: [_declarationImported] :: Declaration -> Maybe [Import]
- HsDev.Symbols.Types: [_declarationModuleId] :: ModuleDeclaration -> ModuleId
- HsDev.Symbols.Types: [_declarationName] :: Declaration -> Text
- HsDev.Symbols.Types: [_declarationPosition] :: Declaration -> Maybe Position
- HsDev.Symbols.Types: [_declaration] :: Declaration -> DeclarationInfo
- HsDev.Symbols.Types: [_exportModule] :: Export -> Text
- HsDev.Symbols.Types: [_exportName] :: Export -> Text
- HsDev.Symbols.Types: [_exportPart] :: Export -> ThingPart
- HsDev.Symbols.Types: [_exportQualified] :: Export -> Maybe Text
- HsDev.Symbols.Types: [_exportedBy] :: ExportedDeclaration -> [ModuleId]
- HsDev.Symbols.Types: [_exportedDeclaration] :: ExportedDeclaration -> Declaration
- HsDev.Symbols.Types: [_hidingList] :: ImportList -> Bool
- HsDev.Symbols.Types: [_importAs] :: Import -> Maybe Text
- HsDev.Symbols.Types: [_importIsQualified] :: Import -> Bool
- HsDev.Symbols.Types: [_importList] :: Import -> Maybe ImportList
- HsDev.Symbols.Types: [_importModuleName] :: Import -> Text
- HsDev.Symbols.Types: [_importPosition] :: Import -> Maybe Position
- HsDev.Symbols.Types: [_importSpecName] :: ImportSpec -> Text
- HsDev.Symbols.Types: [_importSpecPart] :: ImportSpec -> ThingPart
- HsDev.Symbols.Types: [_importSpecs] :: ImportList -> [ImportSpec]
- HsDev.Symbols.Types: [_inspectedId] :: Inspected i t a -> i
- HsDev.Symbols.Types: [_localDeclarations] :: DeclarationInfo -> [Declaration]
- HsDev.Symbols.Types: [_moduleDeclaration] :: ModuleDeclaration -> Declaration
- HsDev.Symbols.Types: [_moduleDeclarations] :: Module -> [Declaration]
- HsDev.Symbols.Types: [_moduleIdLocation] :: ModuleId -> ModuleLocation
- HsDev.Symbols.Types: [_moduleIdName] :: ModuleId -> Text
- HsDev.Symbols.Types: [_moduleLocation] :: Module -> ModuleLocation
- HsDev.Symbols.Types: [_moduleName] :: Module -> Text
- HsDev.Symbols.Types: [_related] :: DeclarationInfo -> Maybe Text
- HsDev.Symbols.Types: [_typeInfoArgs] :: TypeInfo -> [Text]
- HsDev.Symbols.Types: [_typeInfoContext] :: TypeInfo -> Maybe Text
- HsDev.Symbols.Types: [_typeInfoDefinition] :: TypeInfo -> Maybe Text
- HsDev.Symbols.Types: [_typeInfoFunctions] :: TypeInfo -> [Text]
- HsDev.Symbols.Types: [_typeInfo] :: DeclarationInfo -> TypeInfo
- HsDev.Symbols.Types: data Declaration
- HsDev.Symbols.Types: data DeclarationInfo
- HsDev.Symbols.Types: data Export
- HsDev.Symbols.Types: data ExportedDeclaration
- HsDev.Symbols.Types: data Import
- HsDev.Symbols.Types: data ImportList
- HsDev.Symbols.Types: data ImportSpec
- HsDev.Symbols.Types: data ModuleDeclaration
- HsDev.Symbols.Types: data ModuleId
- HsDev.Symbols.Types: data ThingPart
- HsDev.Symbols.Types: data TypeInfo
- HsDev.Symbols.Types: declaration :: Lens' Declaration DeclarationInfo
- HsDev.Symbols.Types: declarationDefined :: Lens' Declaration (Maybe ModuleId)
- HsDev.Symbols.Types: declarationDocs :: Lens' Declaration (Maybe Text)
- HsDev.Symbols.Types: declarationImported :: Lens' Declaration (Maybe [Import])
- HsDev.Symbols.Types: declarationInfo :: DeclarationInfo -> Either (Maybe Text, [Declaration], Maybe Text) TypeInfo
- HsDev.Symbols.Types: declarationModuleId :: Lens' ModuleDeclaration ModuleId
- HsDev.Symbols.Types: declarationName :: Lens' Declaration Text
- HsDev.Symbols.Types: declarationPosition :: Lens' Declaration (Maybe Position)
- HsDev.Symbols.Types: declarationTypeCtor :: String -> TypeInfo -> DeclarationInfo
- HsDev.Symbols.Types: declarationTypeName :: DeclarationInfo -> Maybe String
- HsDev.Symbols.Types: exportModule :: Traversal' Export Text
- HsDev.Symbols.Types: exportName :: Traversal' Export Text
- HsDev.Symbols.Types: exportPart :: Traversal' Export ThingPart
- HsDev.Symbols.Types: exportQualified :: Traversal' Export (Maybe Text)
- HsDev.Symbols.Types: exportedBy :: Lens' ExportedDeclaration [ModuleId]
- HsDev.Symbols.Types: exportedDeclaration :: Lens' ExportedDeclaration Declaration
- HsDev.Symbols.Types: hidingList :: Lens' ImportList Bool
- HsDev.Symbols.Types: importAs :: Lens' Import (Maybe Text)
- HsDev.Symbols.Types: importIsQualified :: Lens' Import Bool
- HsDev.Symbols.Types: importList :: Lens' Import (Maybe ImportList)
- HsDev.Symbols.Types: importModuleName :: Lens' Import Text
- HsDev.Symbols.Types: importPosition :: Lens' Import (Maybe Position)
- HsDev.Symbols.Types: importSpecName :: Lens' ImportSpec Text
- HsDev.Symbols.Types: importSpecPart :: Lens' ImportSpec ThingPart
- HsDev.Symbols.Types: importSpecs :: Lens' ImportList [ImportSpec]
- HsDev.Symbols.Types: inspectedId :: forall i_a1K3e t_a1K3f a_a1K3g i_a1Mdf. Lens (Inspected i_a1K3e t_a1K3f a_a1K3g) (Inspected i_a1Mdf t_a1K3f a_a1K3g) i_a1K3e i_a1Mdf
- HsDev.Symbols.Types: instance (Control.DeepSeq.NFData i, Control.DeepSeq.NFData t, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (HsDev.Symbols.Types.Inspected i t a)
- HsDev.Symbols.Types: instance (GHC.Classes.Eq i, GHC.Classes.Eq t, GHC.Classes.Eq a) => GHC.Classes.Eq (HsDev.Symbols.Types.Inspected i t a)
- HsDev.Symbols.Types: instance (GHC.Classes.Ord i, GHC.Classes.Ord t, GHC.Classes.Ord a) => GHC.Classes.Ord (HsDev.Symbols.Types.Inspected i t a)
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.DeclarationInfo
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.Export
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.ExportedDeclaration
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.Import
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.ImportList
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.ImportSpec
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.ThingPart
- HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.DeclarationInfo
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.Export
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ExportedDeclaration
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.Import
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ImportList
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ImportSpec
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.InspectedModule
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ThingPart
- HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.DeclarationInfo
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.Export
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ExportedDeclaration
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.Import
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ImportList
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ImportSpec
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.InspectedModule
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ThingPart
- HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance Data.Foldable.Foldable (HsDev.Symbols.Types.Inspected i t)
- HsDev.Symbols.Types: instance Data.Traversable.Traversable (HsDev.Symbols.Types.Inspected i t)
- HsDev.Symbols.Types: instance GHC.Base.Functor (HsDev.Symbols.Types.Inspected i t)
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.DeclarationInfo
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.Export
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ExportedDeclaration
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.Import
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ImportList
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ImportSpec
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ThingPart
- HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.DeclarationInfo
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.Export
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ExportedDeclaration
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.Import
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ImportList
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ImportSpec
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ThingPart
- HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance GHC.Read.Read Data.Time.Clock.POSIX.POSIXTime
- HsDev.Symbols.Types: instance GHC.Read.Read HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.Export
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ExportedDeclaration
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.Import
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ImportList
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ImportSpec
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ThingPart
- HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.TypeInfo
- HsDev.Symbols.Types: instance HsDev.Symbols.Class.Symbol HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance HsDev.Symbols.Class.Symbol HsDev.Symbols.Types.Module
- HsDev.Symbols.Types: instance HsDev.Symbols.Class.Symbol HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance HsDev.Symbols.Class.Symbol HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Types.Declaration
- HsDev.Symbols.Types: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Types.ModuleDeclaration
- HsDev.Symbols.Types: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Types.ModuleId
- HsDev.Symbols.Types: localDeclarations :: Traversal' DeclarationInfo [Declaration]
- HsDev.Symbols.Types: minimalDecl :: Lens' Declaration Declaration
- HsDev.Symbols.Types: moduleContents :: Module -> [String]
- HsDev.Symbols.Types: moduleDeclaration :: Lens' ModuleDeclaration Declaration
- HsDev.Symbols.Types: moduleDeclarations :: Lens' Module [Declaration]
- HsDev.Symbols.Types: moduleIdLocation :: Lens' ModuleId ModuleLocation
- HsDev.Symbols.Types: moduleIdName :: Lens' ModuleId Text
- HsDev.Symbols.Types: moduleLocation :: Lens' Module ModuleLocation
- HsDev.Symbols.Types: moduleName :: Lens' Module Text
- HsDev.Symbols.Types: related :: Traversal' DeclarationInfo (Maybe Text)
- HsDev.Symbols.Types: showTypeInfo :: TypeInfo -> String -> String -> String
- HsDev.Symbols.Types: typeInfo :: Traversal' DeclarationInfo TypeInfo
- HsDev.Symbols.Types: typeInfoArgs :: Lens' TypeInfo [Text]
- HsDev.Symbols.Types: typeInfoContext :: Lens' TypeInfo (Maybe Text)
- HsDev.Symbols.Types: typeInfoDefinition :: Lens' TypeInfo (Maybe Text)
- HsDev.Symbols.Types: typeInfoFunctions :: Lens' TypeInfo [Text]
- HsDev.Symbols.Util: allOf :: [a -> Bool] -> a -> Bool
- HsDev.Symbols.Util: anyOf :: [a -> Bool] -> a -> Bool
- HsDev.Symbols.Util: byFile :: ModuleId -> Bool
- HsDev.Symbols.Util: imports :: Module -> [Import]
- HsDev.Symbols.Util: inDepsOfFile :: Project -> FilePath -> ModuleId -> Bool
- HsDev.Symbols.Util: inDepsOfProject :: Project -> ModuleId -> Bool
- HsDev.Symbols.Util: inDepsOfTarget :: Info -> ModuleId -> Bool
- HsDev.Symbols.Util: inFile :: FilePath -> ModuleId -> Bool
- HsDev.Symbols.Util: inModule :: String -> ModuleId -> Bool
- HsDev.Symbols.Util: inModuleSource :: Maybe String -> ModuleId -> Bool
- HsDev.Symbols.Util: inPackage :: String -> ModuleId -> Bool
- HsDev.Symbols.Util: inPackageDb :: PackageDb -> ModuleId -> Bool
- HsDev.Symbols.Util: inPackageDbStack :: PackageDbStack -> ModuleId -> Bool
- HsDev.Symbols.Util: inProject :: Project -> ModuleId -> Bool
- HsDev.Symbols.Util: inScope :: Module -> Maybe String -> ModuleId -> Bool
- HsDev.Symbols.Util: inVersion :: String -> ModuleId -> Bool
- HsDev.Symbols.Util: installed :: ModuleId -> Bool
- HsDev.Symbols.Util: moduleImported :: ModuleId -> [Import] -> Bool
- HsDev.Symbols.Util: newestPackage :: Symbol a => [a] -> [a]
- HsDev.Symbols.Util: packageDbOf :: ModuleId -> Maybe PackageDb
- HsDev.Symbols.Util: packageOf :: ModuleId -> Maybe ModulePackage
- HsDev.Symbols.Util: preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId
- HsDev.Symbols.Util: projectOf :: ModuleId -> Maybe Project
- HsDev.Symbols.Util: qualifier :: Module -> Maybe String -> [Import]
- HsDev.Symbols.Util: sourceModule :: Maybe Project -> [Module] -> Maybe Module
- HsDev.Symbols.Util: standalone :: ModuleId -> Bool
- HsDev.Symbols.Util: uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId]
- HsDev.Symbols.Util: visible :: Project -> ModuleId -> ModuleId -> Bool
- HsDev.Symbols.Util: visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module
- HsDev.Tools.AutoFix: Correction :: String -> Replace String -> Correction
- HsDev.Tools.AutoFix: [_correctionMessage] :: Correction -> String
- HsDev.Tools.AutoFix: [_corrector] :: Correction -> Replace String
- HsDev.Tools.AutoFix: autoFix :: [Note Correction] -> ([Note Correction], Maybe String) -> ([Note Correction], Maybe String)
- HsDev.Tools.AutoFix: correctionMessage :: Lens' Correction String
- HsDev.Tools.AutoFix: corrector :: Lens' Correction (Replace String)
- HsDev.Tools.AutoFix: data Correction
- HsDev.Tools.AutoFix: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Tools.AutoFix.Correction
- HsDev.Tools.AutoFix: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Tools.AutoFix.Correction
- HsDev.Tools.AutoFix: instance GHC.Classes.Eq HsDev.Tools.AutoFix.Correction
- HsDev.Tools.AutoFix: instance GHC.Show.Show HsDev.Tools.AutoFix.Correction
- HsDev.Tools.Base: parseRead :: String -> ReadM a -> Maybe a
- HsDev.Tools.Base: parseReads :: String -> ReadM a -> [a]
- HsDev.Tools.Base: readParse :: Read a => ReadM a
- HsDev.Tools.Base: type ReadM a = StateT String [] a
- HsDev.Tools.Ghc.Check: checkFile :: (MonadLog m, GhcMonad m) => [String] -> Module -> m [Note OutputMessage]
- HsDev.Tools.Ghc.Check: checkFiles :: (MonadLog m, GhcMonad m) => [String] -> [FilePath] -> Maybe Project -> m [Note OutputMessage]
- HsDev.Tools.Ghc.Check: checkSource :: (MonadLog m, GhcMonad m) => [String] -> Module -> String -> m [Note OutputMessage]
- HsDev.Tools.Ghc.Prelude: one :: a -> [a]
- HsDev.Tools.Ghc.Session: ghcSession :: [String] -> GhcM ()
- HsDev.Tools.Ghc.Session: ghciSession :: GhcM ()
- HsDev.Tools.Ghc.Session: haddockSession :: [String] -> GhcM ()
- HsDev.Tools.Ghc.Worker: addCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()
- HsDev.Tools.Ghc.Worker: data SessionTarget
- HsDev.Tools.Ghc.Worker: instance GHC.Classes.Eq HsDev.Tools.Ghc.Worker.SessionTarget
- HsDev.Tools.Ghc.Worker: instance GHC.Classes.Ord HsDev.Tools.Ghc.Worker.SessionTarget
- HsDev.Tools.Ghc.Worker: instance GHC.Show.Show HsDev.Tools.Ghc.Worker.SessionTarget
- HsDev.Tools.Ghc.Worker: instance Text.Format.Formattable HsDev.Tools.Ghc.Worker.SessionTarget
- HsDev.Tools.Ghc.Worker: listPackages :: GhcMonad m => m [ModulePackage]
- HsDev.Tools.Ghc.Worker: setCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()
- HsDev.Tools.HDocs: loadDocs :: [String] -> Module -> IO Module
- HsDev.Tools.HLint: hlintFile :: FilePath -> ExceptT String IO [Note OutputMessage]
- HsDev.Tools.HLint: hlintSource :: FilePath -> String -> ExceptT String IO [Note OutputMessage]
- HsDev.Tools.Hayoo: hayooAsDeclaration :: HayooSymbol -> Maybe ModuleDeclaration
- HsDev.Tools.Hayoo: instance HsDev.Symbols.Class.Symbol HsDev.Tools.Hayoo.HayooSymbol
- HsDev.Util: cutVersion :: Maybe [Int] -> Maybe [Int]
- HsDev.Util: eitherT :: MonadError String m => Either String a -> m a
- HsDev.Util: isParent :: FilePath -> FilePath -> Bool
- HsDev.Util: jsonUnion :: (ToJSON a, ToJSON b) => a -> b -> Value
- HsDev.Util: liftAsync :: (MonadThrow m, MonadCatch m, MonadIO m) => IO (Async a) -> ExceptT String m a
- HsDev.Util: liftEIO :: (MonadCatch m, MonadIO m) => IO a -> ExceptT String m a
- HsDev.Util: liftThrow :: (Show e, MonadError e m, MonadCatch m) => m a -> m a
- HsDev.Util: logException :: String -> (String -> IO ()) -> IO () -> IO ()
- HsDev.Util: sameVersion :: Maybe [Int] -> Maybe [Int] -> Bool
- HsDev.Util: strVersion :: Maybe [Int] -> String
- HsDev.Util: tabs :: Int -> String -> String
- HsDev.Util: withCmd :: String -> (Object -> Parser a) -> Value -> Parser a
- System.Directory.Watcher: events :: Watcher a -> IO [(a, Event)]
- System.Directory.Watcher: onEvent :: Watcher a -> (a -> Event -> IO ()) -> IO ()
+ Control.Concurrent.Util: sync :: MonadIO m => (IO () -> m a) -> m a
+ Control.Concurrent.Worker: joinWorker :: Worker m -> IO ()
+ Control.Concurrent.Worker: workerAlive :: Worker m -> IO Bool
+ Data.Deps: CyclicDeps :: [a] -> DepsError a
+ Data.Deps: instance GHC.Classes.Eq a => GHC.Classes.Eq (Data.Deps.DepsError a)
+ Data.Deps: instance GHC.Classes.Ord a => GHC.Classes.Ord (Data.Deps.DepsError a)
+ Data.Deps: instance GHC.Read.Read a => GHC.Read.Read (Data.Deps.DepsError a)
+ Data.Deps: instance GHC.Show.Show a => GHC.Show.Show (Data.Deps.Deps a)
+ Data.Deps: instance GHC.Show.Show a => GHC.Show.Show (Data.Deps.DepsError a)
+ Data.Deps: linearize :: Ord a => Deps a -> Either (DepsError a) [a]
+ Data.Deps: newtype Deps a
+ Data.Deps: newtype DepsError a
+ Data.Deps: selfDepend :: Deps a -> Deps a
+ Data.LookupTable: cacheInTableM :: (Ord k, MonadIO m) => LookupTable k v -> k -> m v -> m v
+ Data.LookupTable: cachedInTable :: (Ord k, MonadIO m) => LookupTable k v -> (k -> m v) -> k -> m v
+ Data.LookupTable: hasLookupTable :: (Ord k, MonadIO m) => k -> LookupTable k v -> m (Maybe v)
+ Data.LookupTable: lookupTable :: (Ord k, MonadIO m) => k -> v -> LookupTable k v -> m v
+ Data.LookupTable: lookupTableM :: (Ord k, MonadIO m) => k -> m v -> LookupTable k v -> m v
+ Data.LookupTable: newLookupTable :: (Ord k, MonadIO m) => m (LookupTable k v)
+ Data.LookupTable: type LookupTable k v = MVar (Map k v)
+ HsDev.Client.Commands: instance Data.Aeson.Types.FromJSON.FromJSON System.Log.Simple.Base.Message
+ HsDev.Client.Commands: instance Data.Aeson.Types.ToJSON.ToJSON System.Log.Simple.Base.Message
+ HsDev.Database.SQLite: escapeLike :: Text -> Text
+ HsDev.Database.SQLite: execute :: (ToRow q, SessionMonad m) => Query -> q -> m ()
+ HsDev.Database.SQLite: executeMany :: (ToRow q, SessionMonad m) => Query -> [q] -> m ()
+ HsDev.Database.SQLite: executeNamed :: SessionMonad m => Query -> [NamedParam] -> m ()
+ HsDev.Database.SQLite: execute_ :: SessionMonad m => Query -> m ()
+ HsDev.Database.SQLite: initialize :: String -> IO Connection
+ HsDev.Database.SQLite: insertBuildInfo :: SessionMonad m => Info -> m Int
+ HsDev.Database.SQLite: insertModuleSymbols :: SessionMonad m => InspectedModule -> m ()
+ HsDev.Database.SQLite: insertPackageDb :: SessionMonad m => PackageDb -> [ModulePackage] -> m ()
+ HsDev.Database.SQLite: insertProject :: SessionMonad m => Project -> Maybe PackageDbStack -> m ()
+ HsDev.Database.SQLite: lastRow :: SessionMonad m => m Int
+ HsDev.Database.SQLite: loadModule :: SessionMonad m => Int -> m Module
+ HsDev.Database.SQLite: loadModules :: (SessionMonad m, ToRow q) => String -> q -> m [Module]
+ HsDev.Database.SQLite: loadProject :: SessionMonad m => Path -> m Project
+ HsDev.Database.SQLite: lookupModule :: SessionMonad m => ModuleId -> m (Maybe Int)
+ HsDev.Database.SQLite: lookupModuleLocation :: SessionMonad m => ModuleLocation -> m (Maybe Int)
+ HsDev.Database.SQLite: lookupSymbol :: SessionMonad m => Int -> SymbolId -> m (Maybe Int)
+ HsDev.Database.SQLite: privateMemory :: String
+ HsDev.Database.SQLite: purge :: SessionMonad m => m ()
+ HsDev.Database.SQLite: query :: (ToRow q, FromRow r, SessionMonad m) => Query -> q -> m [r]
+ HsDev.Database.SQLite: queryNamed :: (FromRow r, SessionMonad m) => Query -> [NamedParam] -> m [r]
+ HsDev.Database.SQLite: query_ :: (FromRow r, SessionMonad m) => Query -> m [r]
+ HsDev.Database.SQLite: removeModule :: SessionMonad m => Int -> m ()
+ HsDev.Database.SQLite: removeModuleContents :: SessionMonad m => Int -> m ()
+ HsDev.Database.SQLite: removePackageDb :: SessionMonad m => PackageDb -> m ()
+ HsDev.Database.SQLite: removeProject :: SessionMonad m => Project -> m ()
+ HsDev.Database.SQLite: sharedMemory :: String
+ HsDev.Database.SQLite: updateModule :: SessionMonad m => InspectedModule -> m ()
+ HsDev.Database.SQLite: updatePackageDb :: SessionMonad m => PackageDb -> [ModulePackage] -> m ()
+ HsDev.Database.SQLite: updateProject :: SessionMonad m => Project -> Maybe PackageDbStack -> m ()
+ HsDev.Database.SQLite: upsertModule :: SessionMonad m => InspectedModule -> m Int
+ HsDev.Database.SQLite: withTemporaryTable :: SessionMonad m => String -> [String] -> m a -> m a
+ HsDev.Database.SQLite.Instances: JSON :: a -> JSON a
+ HsDev.Database.SQLite.Instances: [getJSON] :: JSON a -> a
+ HsDev.Database.SQLite.Instances: instance Data.Aeson.Types.FromJSON.FromJSON a => Database.SQLite.Simple.FromField.FromField (HsDev.Database.SQLite.Instances.JSON a)
+ HsDev.Database.SQLite.Instances: instance Data.Aeson.Types.ToJSON.ToJSON a => Database.SQLite.Simple.ToField.ToField (HsDev.Database.SQLite.Instances.JSON a)
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromField.FromField Data.Aeson.Types.Internal.Value
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromField.FromField HsDev.PackageDb.Types.PackageDb
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromField.FromField Language.Haskell.Extension.Language
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Project.Types.Executable
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Project.Types.Info
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Project.Types.Library
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Project.Types.Project
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Project.Types.Test
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Location.ModuleId
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Location.ModuleLocation
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Location.ModulePackage
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Location.Position
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Location.Region
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Location.SymbolId
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Types.Inspection
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Types.Symbol
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Symbols.Types.SymbolUsage
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.FromRow.FromRow HsDev.Tools.Ghc.Types.TypedExpr
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToField.ToField Data.Aeson.Types.Internal.Value
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToField.ToField HsDev.PackageDb.Types.PackageDb
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Location.ModuleId
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Location.ModuleLocation
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Location.ModulePackage
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Location.Position
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Location.Region
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Location.SymbolId
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Types.Inspection
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Symbols.Types.Symbol
+ HsDev.Database.SQLite.Instances: instance Database.SQLite.Simple.ToRow.ToRow HsDev.Tools.Ghc.Types.TypedExpr
+ HsDev.Database.SQLite.Instances: instance GHC.Classes.Eq a => GHC.Classes.Eq (HsDev.Database.SQLite.Instances.JSON a)
+ HsDev.Database.SQLite.Instances: instance GHC.Classes.Ord a => GHC.Classes.Ord (HsDev.Database.SQLite.Instances.JSON a)
+ HsDev.Database.SQLite.Instances: instance GHC.Read.Read a => GHC.Read.Read (HsDev.Database.SQLite.Instances.JSON a)
+ HsDev.Database.SQLite.Instances: instance GHC.Show.Show a => GHC.Show.Show (HsDev.Database.SQLite.Instances.JSON a)
+ HsDev.Database.SQLite.Instances: newtype JSON a
+ HsDev.Database.SQLite.Schema: commands :: [Query]
+ HsDev.Database.SQLite.Schema: schema :: Text
+ HsDev.Database.SQLite.Schema.TH: schemaExp :: ExpQ
+ HsDev.Database.SQLite.Select: Select :: [Text] -> [Text] -> [Text] -> Select
+ HsDev.Database.SQLite.Select: [selectColumns] :: Select -> [Text]
+ HsDev.Database.SQLite.Select: [selectConditions] :: Select -> [Text]
+ HsDev.Database.SQLite.Select: [selectTables] :: Select -> [Text]
+ HsDev.Database.SQLite.Select: buildQuery :: Select -> String
+ HsDev.Database.SQLite.Select: data Select
+ HsDev.Database.SQLite.Select: instance GHC.Base.Monoid HsDev.Database.SQLite.Select.Select
+ HsDev.Database.SQLite.Select: instance GHC.Classes.Eq HsDev.Database.SQLite.Select.Select
+ HsDev.Database.SQLite.Select: instance GHC.Classes.Ord HsDev.Database.SQLite.Select.Select
+ HsDev.Database.SQLite.Select: instance GHC.Read.Read HsDev.Database.SQLite.Select.Select
+ HsDev.Database.SQLite.Select: instance GHC.Show.Show HsDev.Database.SQLite.Select.Select
+ HsDev.Database.SQLite.Select: qBuildInfo :: Select
+ HsDev.Database.SQLite.Select: qModuleId :: Select
+ HsDev.Database.SQLite.Select: qModuleLocation :: Select
+ HsDev.Database.SQLite.Select: qSymbol :: Select
+ HsDev.Database.SQLite.Select: qSymbolId :: Select
+ HsDev.Database.SQLite.Select: select_ :: [Text] -> [Text] -> [Text] -> Select
+ HsDev.Database.SQLite.Select: toQuery :: Select -> Query
+ HsDev.Database.SQLite.Select: where_ :: [Text] -> Select
+ HsDev.Database.SQLite.Transaction: Deferred :: TransactionType
+ HsDev.Database.SQLite.Transaction: Exclusive :: TransactionType
+ HsDev.Database.SQLite.Transaction: Immediate :: TransactionType
+ HsDev.Database.SQLite.Transaction: Retries :: [Int] -> (SQLError -> Bool) -> Retries
+ HsDev.Database.SQLite.Transaction: [retriesError] :: Retries -> SQLError -> Bool
+ HsDev.Database.SQLite.Transaction: [retriesIntervals] :: Retries -> [Int]
+ HsDev.Database.SQLite.Transaction: beginTransaction :: MonadIO m => Connection -> TransactionType -> m ()
+ HsDev.Database.SQLite.Transaction: commitTransaction :: MonadIO m => Connection -> m ()
+ HsDev.Database.SQLite.Transaction: data Retries
+ HsDev.Database.SQLite.Transaction: data TransactionType
+ HsDev.Database.SQLite.Transaction: def :: Default a => a
+ HsDev.Database.SQLite.Transaction: instance Data.Default.Class.Default HsDev.Database.SQLite.Transaction.Retries
+ HsDev.Database.SQLite.Transaction: instance GHC.Classes.Eq HsDev.Database.SQLite.Transaction.TransactionType
+ HsDev.Database.SQLite.Transaction: instance GHC.Classes.Ord HsDev.Database.SQLite.Transaction.TransactionType
+ HsDev.Database.SQLite.Transaction: instance GHC.Read.Read HsDev.Database.SQLite.Transaction.TransactionType
+ HsDev.Database.SQLite.Transaction: instance GHC.Show.Show HsDev.Database.SQLite.Transaction.TransactionType
+ HsDev.Database.SQLite.Transaction: noRetry :: Retries
+ HsDev.Database.SQLite.Transaction: retry :: (MonadIO m, MonadCatch m) => Retries -> m a -> m a
+ HsDev.Database.SQLite.Transaction: retryForever :: Int -> Retries
+ HsDev.Database.SQLite.Transaction: retryN :: Int -> Int -> Retries
+ HsDev.Database.SQLite.Transaction: retry_ :: (MonadIO m, MonadCatch m) => m a -> m a
+ HsDev.Database.SQLite.Transaction: rollbackTransaction :: MonadIO m => Connection -> m ()
+ HsDev.Database.SQLite.Transaction: transaction :: SessionMonad m => TransactionType -> Retries -> m a -> m a
+ HsDev.Database.SQLite.Transaction: transaction_ :: SessionMonad m => TransactionType -> m a -> m a
+ HsDev.Database.SQLite.Transaction: withTransaction :: (MonadIO m, MonadMask m) => Connection -> TransactionType -> Retries -> m a -> m a
+ HsDev.Database.Update: applyUpdates :: UpdateOptions -> [(Watched, Event)] -> ClientM IO ()
+ HsDev.Database.Update: processEvents :: ([(Watched, Event)] -> IO ()) -> MVar (Async ()) -> MVar [(Watched, Event)] -> [(Watched, Event)] -> ClientM IO ()
+ HsDev.Database.Update: runTasks_ :: UpdateMonad m => [m ()] -> m ()
+ HsDev.Database.Update: scanFiles :: UpdateMonad m => [(FileSource, [String])] -> m ()
+ HsDev.Database.Update: scanPackageDbStackDocs :: UpdateMonad m => [String] -> PackageDbStack -> m ()
+ HsDev.Database.Update: setModTypes :: UpdateMonad m => ModuleId -> [Note TypedExpr] -> m ()
+ HsDev.Database.Update: updateEvents :: ServerMonadBase m => [(Watched, Event)] -> UpdateM m ()
+ HsDev.Database.Update.Types: UpdateState :: UpdateOptions -> Worker (ServerM IO) -> UpdateState
+ HsDev.Database.Update.Types: [_updateOptions] :: UpdateState -> UpdateOptions
+ HsDev.Database.Update.Types: [_updateWorker] :: UpdateState -> Worker (ServerM IO)
+ HsDev.Database.Update.Types: data UpdateState
+ HsDev.Database.Update.Types: instance GHC.Base.Monad m => Control.Monad.Reader.Class.MonadReader HsDev.Database.Update.Types.UpdateState (HsDev.Database.Update.Types.UpdateM m)
+ HsDev.Database.Update.Types: sendUpdateAction :: UpdateMonad m => ServerM IO () -> m ()
+ HsDev.Database.Update.Types: updateOptions :: Lens' UpdateState UpdateOptions
+ HsDev.Database.Update.Types: updateWorker :: Lens' UpdateState (Worker (ServerM IO))
+ HsDev.Database.Update.Types: withUpdateState :: SessionMonad m => UpdateOptions -> (UpdateState -> m a) -> m a
+ HsDev.Display: instance HsDev.Display.Display HsDev.PackageDb.Types.PackageDb
+ HsDev.Display: instance HsDev.Display.Display HsDev.PackageDb.Types.PackageDbStack
+ HsDev.Display: instance HsDev.Display.Display HsDev.Symbols.Types.ModuleTag
+ HsDev.Display: instance HsDev.Display.Display System.Directory.Paths.Path
+ HsDev.Display: instance Text.Format.Formattable HsDev.PackageDb.Types.PackageDb
+ HsDev.Display: instance Text.Format.Formattable HsDev.PackageDb.Types.PackageDbStack
+ HsDev.Display: instance Text.Format.Formattable HsDev.Sandbox.Sandbox
+ HsDev.Inspect: AnalyzeEnv :: Environment -> Map Name Fixity -> RefineTable -> AnalyzeEnv
+ HsDev.Inspect: Preloaded :: ModuleId -> ParseMode -> Module SrcSpanInfo -> Inspection -> Text -> Preloaded
+ HsDev.Inspect: [_analyzeEnv] :: AnalyzeEnv -> Environment
+ HsDev.Inspect: [_analyzeFixities] :: AnalyzeEnv -> Map Name Fixity
+ HsDev.Inspect: [_analyzeRefine] :: AnalyzeEnv -> RefineTable
+ HsDev.Inspect: [_preloadedId] :: Preloaded -> ModuleId
+ HsDev.Inspect: [_preloadedMode] :: Preloaded -> ParseMode
+ HsDev.Inspect: [_preloadedModule] :: Preloaded -> Module SrcSpanInfo
+ HsDev.Inspect: [_preloadedTime] :: Preloaded -> Inspection
+ HsDev.Inspect: [_preloaded] :: Preloaded -> Text
+ HsDev.Inspect: analyzeEnv :: Lens' AnalyzeEnv Environment
+ HsDev.Inspect: analyzeFixities :: Lens' AnalyzeEnv (Map Name Fixity)
+ HsDev.Inspect: analyzePreloaded :: AnalyzeEnv -> Preloaded -> Either String Module
+ HsDev.Inspect: analyzeRefine :: Lens' AnalyzeEnv RefineTable
+ HsDev.Inspect: analyzeResolve :: AnalyzeEnv -> Module -> Module
+ HsDev.Inspect: asModule :: Lens' Preloaded Module
+ HsDev.Inspect: data AnalyzeEnv
+ HsDev.Inspect: data Preloaded
+ HsDev.Inspect: fileContentsInspection :: [String] -> IO Inspection
+ HsDev.Inspect: fileContentsInspection_ :: [String] -> POSIXTime -> Inspection
+ HsDev.Inspect: installedInspection :: [String] -> IO Inspection
+ HsDev.Inspect: instance Control.DeepSeq.NFData HsDev.Inspect.Preloaded
+ HsDev.Inspect: instance GHC.Base.Monoid HsDev.Inspect.AnalyzeEnv
+ HsDev.Inspect: moduleAnalyzeEnv :: Module -> AnalyzeEnv
+ HsDev.Inspect: moduleInspection :: ModuleLocation -> [String] -> IO Inspection
+ HsDev.Inspect: preload :: Text -> [(String, String)] -> [String] -> ModuleLocation -> Maybe Text -> IO Preloaded
+ HsDev.Inspect: preloaded :: Lens' Preloaded Text
+ HsDev.Inspect: preloadedId :: Lens' Preloaded ModuleId
+ HsDev.Inspect: preloadedMode :: Lens' Preloaded ParseMode
+ HsDev.Inspect: preloadedModule :: Lens' Preloaded (Module SrcSpanInfo)
+ HsDev.Inspect: preloadedTime :: Lens' Preloaded Inspection
+ HsDev.Inspect: sourceInspection :: Path -> Maybe Text -> [String] -> IO Inspection
+ HsDev.Inspect.Order: order :: [Preloaded] -> Either (DepsError Path) [Preloaded]
+ HsDev.PackageDb: packageDbPath :: PackageDb -> IO Path
+ HsDev.PackageDb: readPackageDb :: PackageDb -> IO (Map ModulePackage [ModuleLocation])
+ HsDev.PackageDb.Types: GlobalDb :: PackageDb
+ HsDev.PackageDb.Types: PackageDb :: Path -> PackageDb
+ HsDev.PackageDb.Types: PackageDbStack :: [PackageDb] -> PackageDbStack
+ HsDev.PackageDb.Types: UserDb :: PackageDb
+ HsDev.PackageDb.Types: [_packageDbStack] :: PackageDbStack -> [PackageDb]
+ HsDev.PackageDb.Types: [_packageDb] :: PackageDb -> Path
+ HsDev.PackageDb.Types: data PackageDb
+ HsDev.PackageDb.Types: fromPackageDbs :: [Path] -> PackageDbStack
+ HsDev.PackageDb.Types: globalDb :: PackageDbStack
+ HsDev.PackageDb.Types: instance Control.DeepSeq.NFData HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance Control.DeepSeq.NFData HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: instance GHC.Classes.Eq HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance GHC.Classes.Eq HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: instance GHC.Classes.Ord HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance GHC.Classes.Ord HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: instance GHC.Show.Show HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance GHC.Show.Show HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: instance System.Directory.Paths.Paths HsDev.PackageDb.Types.PackageDb
+ HsDev.PackageDb.Types: instance System.Directory.Paths.Paths HsDev.PackageDb.Types.PackageDbStack
+ HsDev.PackageDb.Types: isSubStack :: PackageDbStack -> PackageDbStack -> Bool
+ HsDev.PackageDb.Types: newtype PackageDbStack
+ HsDev.PackageDb.Types: packageDb :: Traversal' PackageDb Path
+ HsDev.PackageDb.Types: packageDbOpt :: PackageDb -> String
+ HsDev.PackageDb.Types: packageDbStack :: Iso' PackageDbStack [PackageDb]
+ HsDev.PackageDb.Types: packageDbStackOpts :: PackageDbStack -> [String]
+ HsDev.PackageDb.Types: packageDbStacks :: PackageDbStack -> [PackageDbStack]
+ HsDev.PackageDb.Types: packageDbs :: PackageDbStack -> [PackageDb]
+ HsDev.PackageDb.Types: topPackageDb :: PackageDbStack -> PackageDb
+ HsDev.PackageDb.Types: userDb :: PackageDbStack
+ HsDev.Project: fileInTarget :: Path -> Info -> Bool
+ HsDev.Project: projectTargetFiles :: (MonadLog m, Target t) => Project -> t -> m [Path]
+ HsDev.Project: targetFiles :: Target t => t -> [Path]
+ HsDev.Project.Types: TargetInfo :: Maybe Text -> Info -> Maybe Path -> [[Text]] -> TargetInfo
+ HsDev.Project.Types: [_infoOtherModules] :: Info -> [[Text]]
+ HsDev.Project.Types: [_targetBuildInfo] :: TargetInfo -> Info
+ HsDev.Project.Types: [_targetInfoMain] :: TargetInfo -> Maybe Path
+ HsDev.Project.Types: [_targetInfoModules] :: TargetInfo -> [[Text]]
+ HsDev.Project.Types: [_targetInfoName] :: TargetInfo -> Maybe Text
+ HsDev.Project.Types: [_testMain] :: Test -> Maybe Path
+ HsDev.Project.Types: data TargetInfo
+ HsDev.Project.Types: infoOtherModules :: Lens' Info [[Text]]
+ HsDev.Project.Types: infos :: Traversal' ProjectDescription Info
+ HsDev.Project.Types: instance Data.Aeson.Types.FromJSON.FromJSON Language.Haskell.Extension.Extension
+ HsDev.Project.Types: instance Data.Aeson.Types.FromJSON.FromJSON Language.Haskell.Extension.Language
+ HsDev.Project.Types: instance Data.Aeson.Types.ToJSON.ToJSON Language.Haskell.Extension.Extension
+ HsDev.Project.Types: instance Data.Aeson.Types.ToJSON.ToJSON Language.Haskell.Extension.Language
+ HsDev.Project.Types: instance GHC.Classes.Eq HsDev.Project.Types.TargetInfo
+ HsDev.Project.Types: instance GHC.Classes.Ord HsDev.Project.Types.TargetInfo
+ HsDev.Project.Types: instance GHC.Show.Show HsDev.Project.Types.TargetInfo
+ HsDev.Project.Types: instance HsDev.Project.Types.Target HsDev.Project.Types.TargetInfo
+ HsDev.Project.Types: instance System.Directory.Paths.Paths HsDev.Project.Types.TargetInfo
+ HsDev.Project.Types: targetBuildInfo :: Lens' TargetInfo Info
+ HsDev.Project.Types: targetInfo :: Target a => a -> TargetInfo
+ HsDev.Project.Types: targetInfoMain :: Lens' TargetInfo (Maybe Path)
+ HsDev.Project.Types: targetInfoModules :: Lens' TargetInfo [[Text]]
+ HsDev.Project.Types: targetInfoName :: Lens' TargetInfo (Maybe Text)
+ HsDev.Project.Types: targetInfos :: ProjectDescription -> [TargetInfo]
+ HsDev.Project.Types: targetMain :: Target a => a -> Maybe Path
+ HsDev.Project.Types: targetModules :: Target a => a -> [[Text]]
+ HsDev.Project.Types: targetName :: Target a => Traversal' a Text
+ HsDev.Project.Types: testMain :: Lens' Test (Maybe Path)
+ HsDev.Sandbox: getProjectPackageDbStack :: Project -> GhcM PackageDbStack
+ HsDev.Sandbox: getProjectSandbox :: MonadLog m => Project -> m (Maybe Sandbox)
+ HsDev.Sandbox: getProjectTargetOpts :: [String] -> Project -> Info -> GhcM [String]
+ HsDev.Scan: enumDependent :: CommandMonad m => FilePath -> m ScanContents
+ HsDev.Scan: enumRescan :: CommandMonad m => FilePath -> m ScanContents
+ HsDev.Scan: getFileContents :: SessionMonad m => Path -> m (Maybe (POSIXTime, Text))
+ HsDev.Scan: instance HsDev.Scan.EnumContents HsDev.PackageDb.Types.PackageDbStack
+ HsDev.Scan: instance HsDev.Scan.EnumContents System.Directory.Paths.Path
+ HsDev.Scan.Browse: browseModules' :: [String] -> [ModuleLocation] -> GhcM [InspectedModule]
+ HsDev.Scan.Browse: modulesPackages :: [ModuleLocation] -> [ModulePackage]
+ HsDev.Scan.Browse: modulesPackagesGroups :: [ModuleLocation] -> [(ModulePackage, [ModuleLocation])]
+ HsDev.Scan.Browse: withEachPackage :: (ModulePackage -> [ModuleLocation] -> GhcM a) -> [ModuleLocation] -> GhcM [a]
+ HsDev.Server.Base: clientCommand :: CommandOptions -> Command -> ServerM IO Result
+ HsDev.Server.Base: findPath :: MonadIO m => CommandOptions -> FilePath -> m FilePath
+ HsDev.Server.Base: makeSocket :: ConnectionPort -> IO Socket
+ HsDev.Server.Base: parseCommand :: [String] -> Either String Command
+ HsDev.Server.Base: processClient :: SessionMonad m => String -> Chan ByteString -> (ByteString -> IO ()) -> m ()
+ HsDev.Server.Base: processClientSocket :: SessionMonad m => String -> Socket -> m ()
+ HsDev.Server.Base: processRequest :: SessionMonad m => CommandOptions -> Command -> m Result
+ HsDev.Server.Base: readCommand :: [String] -> Command
+ HsDev.Server.Base: sendServer :: Server -> CommandOptions -> [String] -> IO Result
+ HsDev.Server.Base: sendServer_ :: Server -> [String] -> IO Result
+ HsDev.Server.Base: setupServer :: ServerOpts -> ServerM IO ()
+ HsDev.Server.Base: shutdownServer :: ServerOpts -> ServerM IO ()
+ HsDev.Server.Base: sockAddr :: ConnectionPort -> HostAddress -> SockAddr
+ HsDev.Server.Base: startServer_ :: ServerOpts -> IO Server
+ HsDev.Server.Base: stopServer :: Server -> IO ()
+ HsDev.Server.Base: unMmap :: Response -> IO Response
+ HsDev.Server.Base: withServer :: ServerOpts -> (Server -> IO a) -> IO a
+ HsDev.Server.Base: withServer_ :: ServerOpts -> (Server -> IO a) -> IO a
+ HsDev.Server.Commands: [serverDbFile] :: ServerOpts -> Maybe FilePath
+ HsDev.Server.Message: decodeMessage :: FromJSON a => ByteString -> Either (Msg String) (Msg (Message a))
+ HsDev.Server.Message: encodeMessage :: ToJSON a => Msg (Message a) -> ByteString
+ HsDev.Server.Message.Lisp: decodeMsg :: FromJSON a => ByteString -> Either (Msg String) (Msg a)
+ HsDev.Server.Message.Lisp: encodeMsg :: ToJSON a => Msg a -> ByteString
+ HsDev.Server.Message.Lisp: isLisp :: Lens' (Msg a) Bool
+ HsDev.Server.Message.Lisp: jsonMsg :: a -> Msg a
+ HsDev.Server.Message.Lisp: lispMsg :: a -> Msg a
+ HsDev.Server.Message.Lisp: msg :: Lens (Msg a) (Msg b) a b
+ HsDev.Server.Message.Lisp: type Msg a = (Bool, a)
+ HsDev.Server.Types: FindUsages :: Text -> Command
+ HsDev.Server.Types: Refactor :: [Note Refact] -> [Note Refact] -> Bool -> Command
+ HsDev.Server.Types: Rename :: Text -> Text -> Path -> Command
+ HsDev.Server.Types: SetFileContents :: Path -> (Maybe Text) -> Command
+ HsDev.Server.Types: StopGhc :: Command
+ HsDev.Server.Types: TargetInstalled :: TargetFilter
+ HsDev.Server.Types: UnresolvedSymbols :: [Path] -> Command
+ HsDev.Server.Types: Whoat :: Int -> Int -> Path -> Command
+ HsDev.Server.Types: [checkClear] :: Command -> Bool
+ HsDev.Server.Types: [checkLinkClear] :: Command -> Bool
+ HsDev.Server.Types: [serverDbFile] :: ServerOpts -> Maybe FilePath
+ HsDev.Server.Types: [sessionClients] :: Session -> Chan (IO ())
+ HsDev.Server.Types: [sessionFileContents] :: Session -> Path -> Maybe Text -> IO ()
+ HsDev.Server.Types: [sessionSqlDatabase] :: Session -> Connection
+ HsDev.Server.Types: [sessionSqlPath] :: Session -> String
+ HsDev.Server.Types: [sessionUpdater] :: Session -> Worker (ServerM IO)
+ HsDev.Server.Types: [typesClear] :: Command -> Bool
+ HsDev.Server.Types: [unresolvedFiles] :: Command -> [Path]
+ HsDev.Server.Types: closeSqlConnection :: SessionMonad m => Connection -> m ()
+ HsDev.Server.Types: inSessionUpdater :: SessionMonad m => ServerM IO a -> m a
+ HsDev.Server.Types: instance (HsDev.Server.Types.CommandMonad m, GHC.Base.Monoid w) => HsDev.Server.Types.CommandMonad (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ HsDev.Server.Types: instance (HsDev.Server.Types.SessionMonad m, GHC.Base.Monoid w) => HsDev.Server.Types.SessionMonad (Control.Monad.Trans.Writer.Lazy.WriterT w m)
+ HsDev.Server.Types: instance Control.Monad.Morph.MFunctor HsDev.Server.Types.ClientM
+ HsDev.Server.Types: instance Control.Monad.Morph.MFunctor HsDev.Server.Types.ServerM
+ HsDev.Server.Types: instance HsDev.Server.Types.CommandMonad m => HsDev.Server.Types.CommandMonad (Control.Monad.Trans.Reader.ReaderT r m)
+ HsDev.Server.Types: instance HsDev.Server.Types.CommandMonad m => HsDev.Server.Types.CommandMonad (Control.Monad.Trans.State.Lazy.StateT s m)
+ HsDev.Server.Types: instance HsDev.Server.Types.SessionMonad m => HsDev.Server.Types.SessionMonad (Control.Monad.Trans.Reader.ReaderT r m)
+ HsDev.Server.Types: instance HsDev.Server.Types.SessionMonad m => HsDev.Server.Types.SessionMonad (Control.Monad.Trans.State.Lazy.StateT s m)
+ HsDev.Server.Types: localSession :: SessionMonad m => (Session -> Session) -> m a -> m a
+ HsDev.Server.Types: openSqlConnection :: SessionMonad m => m Connection
+ HsDev.Server.Types: serverSetFileContents :: SessionMonad m => Path -> Maybe Text -> m ()
+ HsDev.Server.Types: serverSqlDatabase :: SessionMonad m => m Connection
+ HsDev.Server.Types: serverWaitClients :: SessionMonad m => m ()
+ HsDev.Server.Types: withSqlConnection :: SessionMonad m => m a -> m a
+ HsDev.Server.Types: withSqlTransaction :: SessionMonad m => ServerM IO a -> m a
+ HsDev.Symbols: projectTargetOpts :: [PackageConfig] -> Project -> Info -> [String]
+ HsDev.Symbols.Class: class Sourced a
+ HsDev.Symbols.Class: instance HsDev.Symbols.Class.Sourced HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Class: instance HsDev.Symbols.Class.Sourced HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Class: sourcedDocs :: Sourced a => Traversal' a Text
+ HsDev.Symbols.Class: sourcedLocation :: Sourced a => Traversal' a Position
+ HsDev.Symbols.Class: sourcedModule :: Sourced a => Lens' a ModuleId
+ HsDev.Symbols.Class: sourcedModuleName :: Sourced a => Lens' a Text
+ HsDev.Symbols.Class: sourcedName :: Sourced a => Lens' a Text
+ HsDev.Symbols.Documented: instance HsDev.Symbols.Documented.Documented HsDev.Project.Types.Project
+ HsDev.Symbols.Documented: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Location.ModuleLocation
+ HsDev.Symbols.Documented: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Location.ModulePackage
+ HsDev.Symbols.HaskellNames: class ToEnvironment a
+ HsDev.Symbols.HaskellNames: environment :: ToEnvironment a => a -> Environment
+ HsDev.Symbols.HaskellNames: fromSymbol :: Symbol -> Symbol
+ HsDev.Symbols.HaskellNames: instance HsDev.Symbols.HaskellNames.ToEnvironment HsDev.Symbols.Types.Module
+ HsDev.Symbols.HaskellNames: instance HsDev.Symbols.HaskellNames.ToEnvironment [HsDev.Symbols.Types.Module]
+ HsDev.Symbols.HaskellNames: toSymbol :: Symbol -> Symbol
+ HsDev.Symbols.Location: ModuleId :: Text -> ModuleLocation -> ModuleId
+ HsDev.Symbols.Location: NoLocation :: ModuleLocation
+ HsDev.Symbols.Location: OtherLocation :: Text -> ModuleLocation
+ HsDev.Symbols.Location: SymbolId :: Text -> ModuleId -> SymbolId
+ HsDev.Symbols.Location: [_installedModuleName] :: ModuleLocation -> Text
+ HsDev.Symbols.Location: [_moduleInstallDirs] :: ModuleLocation -> [Path]
+ HsDev.Symbols.Location: [_moduleLocation] :: ModuleId -> ModuleLocation
+ HsDev.Symbols.Location: [_moduleName] :: ModuleId -> Text
+ HsDev.Symbols.Location: [_otherLocationName] :: ModuleLocation -> Text
+ HsDev.Symbols.Location: [_symbolModule] :: SymbolId -> ModuleId
+ HsDev.Symbols.Location: [_symbolName] :: SymbolId -> Text
+ HsDev.Symbols.Location: data ModuleId
+ HsDev.Symbols.Location: data SymbolId
+ HsDev.Symbols.Location: importPath :: Text -> Path
+ HsDev.Symbols.Location: installedModuleName :: Traversal' ModuleLocation Text
+ HsDev.Symbols.Location: instance Control.DeepSeq.NFData HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Location: instance Control.DeepSeq.NFData HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Location: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Location: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Location: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Location: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Location: instance GHC.Classes.Eq HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Location: instance GHC.Classes.Eq HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Location: instance GHC.Classes.Ord HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Location: instance GHC.Classes.Ord HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Location: instance GHC.Show.Show HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Location: instance GHC.Show.Show HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Location: mkPackage :: Text -> ModulePackage
+ HsDev.Symbols.Location: moduleInstallDirs :: Traversal' ModuleLocation [Path]
+ HsDev.Symbols.Location: moduleLocation :: Lens' ModuleId ModuleLocation
+ HsDev.Symbols.Location: moduleName :: Lens' ModuleId Text
+ HsDev.Symbols.Location: otherLocationName :: Traversal' ModuleLocation Text
+ HsDev.Symbols.Location: sourceRoot :: ModuleId -> Maybe Path
+ HsDev.Symbols.Location: sourceRoot_ :: ModuleId -> Path
+ HsDev.Symbols.Location: symbolModule :: Lens' SymbolId ModuleId
+ HsDev.Symbols.Location: symbolName :: Lens' SymbolId Text
+ HsDev.Symbols.Name: fromModuleName_ :: ModuleName () -> Text
+ HsDev.Symbols.Name: fromName :: Name -> Text
+ HsDev.Symbols.Name: fromName_ :: Name () -> Text
+ HsDev.Symbols.Name: nameIdent :: Name -> Text
+ HsDev.Symbols.Name: nameModule :: Name -> Maybe Text
+ HsDev.Symbols.Name: qualName :: String -> String -> Name
+ HsDev.Symbols.Name: toModuleName_ :: Text -> ModuleName ()
+ HsDev.Symbols.Name: toName :: Text -> Name
+ HsDev.Symbols.Name: toName_ :: Text -> Name ()
+ HsDev.Symbols.Name: type Name = QName ()
+ HsDev.Symbols.Name: unqualName :: String -> Name
+ HsDev.Symbols.Parsed: annL :: Annotated ast => Lens' (ast a) a
+ HsDev.Symbols.Parsed: binders :: Annotated ast => Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Parsed: declarations :: Data (ast Ann) => Traversal' (ast Ann) (Decl Ann)
+ HsDev.Symbols.Parsed: defPos :: Annotated ast => Traversal' (ast Ann) Position
+ HsDev.Symbols.Parsed: file :: Annotated ast => Lens' (ast Ann) FilePath
+ HsDev.Symbols.Parsed: fileL :: (SrcInfo isrc, Data isrc) => Lens' isrc FilePath
+ HsDev.Symbols.Parsed: globals :: Annotated ast => Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Parsed: imports :: Data (ast Ann) => Traversal' (ast Ann) (ImportDecl Ann)
+ HsDev.Symbols.Parsed: isBinder :: Annotated ast => ast Ann -> Bool
+ HsDev.Symbols.Parsed: isGlobal :: Annotated ast => ast Ann -> Bool
+ HsDev.Symbols.Parsed: isLocal :: Annotated ast => ast Ann -> Bool
+ HsDev.Symbols.Parsed: isReference :: Annotated ast => ast Ann -> Bool
+ HsDev.Symbols.Parsed: isUnresolved :: Annotated ast => ast Ann -> Bool
+ HsDev.Symbols.Parsed: locals :: Annotated ast => Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Parsed: moduleNames :: Data (ast Ann) => Traversal' (ast Ann) (ModuleName Ann)
+ HsDev.Symbols.Parsed: nameInfoL :: Lens' (Scoped a) (NameInfo a)
+ HsDev.Symbols.Parsed: named :: Annotated ast => Text -> Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Parsed: names :: Data (ast Ann) => Traversal' (ast Ann) (Name Ann)
+ HsDev.Symbols.Parsed: pos :: Annotated ast => Lens' (ast Ann) Position
+ HsDev.Symbols.Parsed: positionL :: (SrcInfo isrc, Data isrc) => Lens' isrc Position
+ HsDev.Symbols.Parsed: prettyPrint :: Pretty a => a -> String
+ HsDev.Symbols.Parsed: qnames :: Data (ast Ann) => Traversal' (ast Ann) (QName Ann)
+ HsDev.Symbols.Parsed: references :: Annotated ast => Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Parsed: refsTo :: Annotated ast => Name -> ast Ann -> Bool
+ HsDev.Symbols.Parsed: refsToName :: Annotated ast => Text -> ast Ann -> Bool
+ HsDev.Symbols.Parsed: regionL :: Annotated ast => Lens' (ast Ann) Region
+ HsDev.Symbols.Parsed: resolveError :: Annotated ast => ast Ann -> Maybe String
+ HsDev.Symbols.Parsed: resolvedName :: Annotated ast => Traversal' (ast Ann) Name
+ HsDev.Symbols.Parsed: symbolL :: Data a => Traversal' a Symbol
+ HsDev.Symbols.Parsed: symbolNameL :: Lens' Symbol Name
+ HsDev.Symbols.Parsed: type Ann = Scoped SrcSpanInfo
+ HsDev.Symbols.Parsed: type Parsed = Module Ann
+ HsDev.Symbols.Parsed: unresolveds :: Annotated ast => Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Parsed: usages :: Annotated ast => Name -> Traversal' (ast Ann) (ast Ann)
+ HsDev.Symbols.Resolve: refineSymbol :: RefineTable -> Symbol -> Symbol
+ HsDev.Symbols.Resolve: refineSymbols :: RefineTable -> Module -> Module
+ HsDev.Symbols.Resolve: refineTable :: [Symbol] -> RefineTable
+ HsDev.Symbols.Resolve: symbolUniqId :: Symbol -> (Text, Text, SymbolInfo)
+ HsDev.Symbols.Resolve: type RefineTable = Map (Text, Text, SymbolInfo) Symbol
+ HsDev.Symbols.Types: Constructor :: [Text] -> Text -> SymbolInfo
+ HsDev.Symbols.Types: DataFam :: [Text] -> [Text] -> Maybe Text -> SymbolInfo
+ HsDev.Symbols.Types: Method :: Maybe Text -> Text -> SymbolInfo
+ HsDev.Symbols.Types: OnlyHeaderTag :: ModuleTag
+ HsDev.Symbols.Types: PatConstructor :: [Text] -> Maybe Text -> SymbolInfo
+ HsDev.Symbols.Types: PatSelector :: Maybe Text -> Maybe Text -> Text -> SymbolInfo
+ HsDev.Symbols.Types: ScopeSymbol :: Maybe Text -> SymbolId -> ScopeSymbol
+ HsDev.Symbols.Types: Selector :: Maybe Text -> Text -> [Text] -> SymbolInfo
+ HsDev.Symbols.Types: Symbol :: SymbolId -> Maybe Text -> Maybe Position -> SymbolInfo -> Symbol
+ HsDev.Symbols.Types: SymbolUsage :: Symbol -> ModuleId -> Position -> SymbolUsage
+ HsDev.Symbols.Types: TypeFam :: [Text] -> [Text] -> Maybe Text -> SymbolInfo
+ HsDev.Symbols.Types: [_familyAssociate] :: SymbolInfo -> Maybe Text
+ HsDev.Symbols.Types: [_inspectedKey] :: Inspected k t a -> k
+ HsDev.Symbols.Types: [_moduleFixities] :: Module -> [Fixity]
+ HsDev.Symbols.Types: [_moduleId] :: Module -> ModuleId
+ HsDev.Symbols.Types: [_moduleScope] :: Module -> Map Name [Symbol]
+ HsDev.Symbols.Types: [_moduleSource] :: Module -> Maybe Parsed
+ HsDev.Symbols.Types: [_parentClass] :: SymbolInfo -> Text
+ HsDev.Symbols.Types: [_parentType] :: SymbolInfo -> Text
+ HsDev.Symbols.Types: [_patternConstructor] :: SymbolInfo -> Text
+ HsDev.Symbols.Types: [_patternType] :: SymbolInfo -> Maybe Text
+ HsDev.Symbols.Types: [_scopeQualifier] :: ScopeSymbol -> Maybe Text
+ HsDev.Symbols.Types: [_scopeSymbol] :: ScopeSymbol -> SymbolId
+ HsDev.Symbols.Types: [_selectorConstructors] :: SymbolInfo -> [Text]
+ HsDev.Symbols.Types: [_symbolDocs] :: Symbol -> Maybe Text
+ HsDev.Symbols.Types: [_symbolId] :: Symbol -> SymbolId
+ HsDev.Symbols.Types: [_symbolInfo] :: Symbol -> SymbolInfo
+ HsDev.Symbols.Types: [_symbolPosition] :: Symbol -> Maybe Position
+ HsDev.Symbols.Types: [_symbolUsedIn] :: SymbolUsage -> ModuleId
+ HsDev.Symbols.Types: [_symbolUsedPosition] :: SymbolUsage -> Position
+ HsDev.Symbols.Types: [_symbolUsed] :: SymbolUsage -> Symbol
+ HsDev.Symbols.Types: [_typeArgs] :: SymbolInfo -> [Text]
+ HsDev.Symbols.Types: [_typeContext] :: SymbolInfo -> [Text]
+ HsDev.Symbols.Types: data ScopeSymbol
+ HsDev.Symbols.Types: data Symbol
+ HsDev.Symbols.Types: data SymbolInfo
+ HsDev.Symbols.Types: data SymbolUsage
+ HsDev.Symbols.Types: exportedSymbols :: Traversal' Module Symbol
+ HsDev.Symbols.Types: familyAssociate :: Traversal' SymbolInfo (Maybe Text)
+ HsDev.Symbols.Types: fixitiesMap :: Lens' Module (Map Name Fixity)
+ HsDev.Symbols.Types: fresh :: Inspection -> Inspection -> Bool
+ HsDev.Symbols.Types: infoOf :: EmptySymbolInfo a => a -> SymbolInfo
+ HsDev.Symbols.Types: inspected :: Traversal (Inspected k t a) (Inspected k t b) a b
+ HsDev.Symbols.Types: inspectedKey :: forall k_a22lU t_a22lV a_a22lW k_a24tX. Lens (Inspected k_a22lU t_a22lV a_a22lW) (Inspected k_a24tX t_a22lV a_a22lW) k_a22lU k_a24tX
+ HsDev.Symbols.Types: inspectedTup :: Inspected k t a -> (Inspection, k, Set t, Maybe a)
+ HsDev.Symbols.Types: instance (Control.DeepSeq.NFData k, Control.DeepSeq.NFData t, Control.DeepSeq.NFData a) => Control.DeepSeq.NFData (HsDev.Symbols.Types.Inspected k t a)
+ HsDev.Symbols.Types: instance (Data.Aeson.Types.FromJSON.FromJSON k, GHC.Classes.Ord t, Data.Aeson.Types.FromJSON.FromJSON t, Data.Aeson.Types.FromJSON.FromJSON a) => Data.Aeson.Types.FromJSON.FromJSON (HsDev.Symbols.Types.Inspected k t a)
+ HsDev.Symbols.Types: instance (Data.Aeson.Types.ToJSON.ToJSON k, Data.Aeson.Types.ToJSON.ToJSON t, Data.Aeson.Types.ToJSON.ToJSON a) => Data.Aeson.Types.ToJSON.ToJSON (HsDev.Symbols.Types.Inspected k t a)
+ HsDev.Symbols.Types: instance (GHC.Base.Monoid a, HsDev.Symbols.Types.EmptySymbolInfo r) => HsDev.Symbols.Types.EmptySymbolInfo (a -> r)
+ HsDev.Symbols.Types: instance (GHC.Classes.Eq k, GHC.Classes.Eq t, GHC.Classes.Eq a) => GHC.Classes.Eq (HsDev.Symbols.Types.Inspected k t a)
+ HsDev.Symbols.Types: instance (GHC.Classes.Ord k, GHC.Classes.Ord t, GHC.Classes.Ord a) => GHC.Classes.Ord (HsDev.Symbols.Types.Inspected k t a)
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData (Language.Haskell.Exts.Syntax.Assoc ())
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData Language.Haskell.Exts.Fixity.Fixity
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData Language.Haskell.Exts.Syntax.Boxed
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData l => Control.DeepSeq.NFData (Language.Haskell.Exts.Syntax.ModuleName l)
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData l => Control.DeepSeq.NFData (Language.Haskell.Exts.Syntax.Name l)
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData l => Control.DeepSeq.NFData (Language.Haskell.Exts.Syntax.QName l)
+ HsDev.Symbols.Types: instance Control.DeepSeq.NFData l => Control.DeepSeq.NFData (Language.Haskell.Exts.Syntax.SpecialCon l)
+ HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON (Language.Haskell.Exts.Syntax.Assoc ())
+ HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.ScopeSymbol
+ HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Symbols.Types.SymbolUsage
+ HsDev.Symbols.Types: instance Data.Aeson.Types.FromJSON.FromJSON Language.Haskell.Exts.Fixity.Fixity
+ HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON (Language.Haskell.Exts.Syntax.Assoc ())
+ HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.ScopeSymbol
+ HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Symbols.Types.SymbolUsage
+ HsDev.Symbols.Types: instance Data.Aeson.Types.ToJSON.ToJSON Language.Haskell.Exts.Fixity.Fixity
+ HsDev.Symbols.Types: instance Data.Foldable.Foldable (HsDev.Symbols.Types.Inspected k t)
+ HsDev.Symbols.Types: instance Data.Traversable.Traversable (HsDev.Symbols.Types.Inspected k t)
+ HsDev.Symbols.Types: instance GHC.Base.Functor (HsDev.Symbols.Types.Inspected k t)
+ HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.ScopeSymbol
+ HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance GHC.Classes.Eq HsDev.Symbols.Types.SymbolUsage
+ HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.ScopeSymbol
+ HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance GHC.Classes.Ord HsDev.Symbols.Types.SymbolUsage
+ HsDev.Symbols.Types: instance GHC.Read.Read Data.Time.Clock.Internal.POSIXTime.POSIXTime
+ HsDev.Symbols.Types: instance GHC.Read.Read HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.ScopeSymbol
+ HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: instance GHC.Show.Show HsDev.Symbols.Types.SymbolUsage
+ HsDev.Symbols.Types: instance HsDev.Symbols.Class.Sourced HsDev.Symbols.Types.Module
+ HsDev.Symbols.Types: instance HsDev.Symbols.Class.Sourced HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Location.ModuleId
+ HsDev.Symbols.Types: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Location.SymbolId
+ HsDev.Symbols.Types: instance HsDev.Symbols.Documented.Documented HsDev.Symbols.Types.Symbol
+ HsDev.Symbols.Types: instance HsDev.Symbols.Types.EmptySymbolInfo HsDev.Symbols.Types.SymbolInfo
+ HsDev.Symbols.Types: moduleFixities :: Lens' Module [Fixity]
+ HsDev.Symbols.Types: moduleScope :: Lens' Module (Map Name [Symbol])
+ HsDev.Symbols.Types: moduleSource :: Lens' Module (Maybe Parsed)
+ HsDev.Symbols.Types: moduleSymbols :: Traversal' Module Symbol
+ HsDev.Symbols.Types: nullifyInfo :: SymbolInfo -> SymbolInfo
+ HsDev.Symbols.Types: parentClass :: Traversal' SymbolInfo Text
+ HsDev.Symbols.Types: parentType :: Traversal' SymbolInfo Text
+ HsDev.Symbols.Types: patternConstructor :: Traversal' SymbolInfo Text
+ HsDev.Symbols.Types: patternType :: Traversal' SymbolInfo (Maybe Text)
+ HsDev.Symbols.Types: scopeQualifier :: Lens' ScopeSymbol (Maybe Text)
+ HsDev.Symbols.Types: scopeSymbol :: Lens' ScopeSymbol SymbolId
+ HsDev.Symbols.Types: scopeSymbols :: Traversal' Module (Symbol, [Name])
+ HsDev.Symbols.Types: selectorConstructors :: Traversal' SymbolInfo [Text]
+ HsDev.Symbols.Types: symbolDocs :: Lens' Symbol (Maybe Text)
+ HsDev.Symbols.Types: symbolId :: Lens' Symbol SymbolId
+ HsDev.Symbols.Types: symbolInfo :: Lens' Symbol SymbolInfo
+ HsDev.Symbols.Types: symbolPosition :: Lens' Symbol (Maybe Position)
+ HsDev.Symbols.Types: symbolType :: Symbol -> String
+ HsDev.Symbols.Types: symbolUsed :: Lens' SymbolUsage Symbol
+ HsDev.Symbols.Types: symbolUsedIn :: Lens' SymbolUsage ModuleId
+ HsDev.Symbols.Types: symbolUsedPosition :: Lens' SymbolUsage Position
+ HsDev.Symbols.Types: tag :: t -> Set t
+ HsDev.Symbols.Types: typeArgs :: Traversal' SymbolInfo [Text]
+ HsDev.Symbols.Types: typeContext :: Traversal' SymbolInfo [Text]
+ HsDev.Tools.AutoFix: instance Data.Text.Region.Types.Regioned a => Data.Text.Region.Types.Regioned (HsDev.Tools.Types.Note a)
+ HsDev.Tools.Ghc.Compat: getFixity :: Fixity -> (Int, FixityDirection)
+ HsDev.Tools.Ghc.Compat: recSelCtors :: RecSelParent -> [String]
+ HsDev.Tools.Ghc.Compat: recSelParent :: RecSelParent -> String
+ HsDev.Tools.Ghc.MGhc: currentSession :: MonadIO m => MGhcT s m (Maybe s)
+ HsDev.Tools.Ghc.MGhc: tempSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => s -> MGhcT s m a -> MGhcT s m a
+ HsDev.Tools.Ghc.Worker: SessionConfig :: SessionType -> [String] -> SessionConfig
+ HsDev.Tools.Ghc.Worker: SessionHaddock :: SessionType
+ HsDev.Tools.Ghc.Worker: SessionTmp :: SessionType
+ HsDev.Tools.Ghc.Worker: data SessionConfig
+ HsDev.Tools.Ghc.Worker: data SessionType
+ HsDev.Tools.Ghc.Worker: ghcRunWith :: GhcMonad m => (DynFlags -> DynFlags) -> [String] -> m a -> m a
+ HsDev.Tools.Ghc.Worker: ghcSession :: [String] -> GhcM ()
+ HsDev.Tools.Ghc.Worker: ghciSession :: GhcM ()
+ HsDev.Tools.Ghc.Worker: haddockSession :: [String] -> GhcM ()
+ HsDev.Tools.Ghc.Worker: instance GHC.Classes.Eq HsDev.Tools.Ghc.Worker.SessionConfig
+ HsDev.Tools.Ghc.Worker: instance GHC.Classes.Eq HsDev.Tools.Ghc.Worker.SessionType
+ HsDev.Tools.Ghc.Worker: instance GHC.Classes.Ord HsDev.Tools.Ghc.Worker.SessionConfig
+ HsDev.Tools.Ghc.Worker: instance GHC.Classes.Ord HsDev.Tools.Ghc.Worker.SessionType
+ HsDev.Tools.Ghc.Worker: instance GHC.Show.Show HsDev.Tools.Ghc.Worker.SessionConfig
+ HsDev.Tools.Ghc.Worker: instance GHC.Show.Show HsDev.Tools.Ghc.Worker.SessionType
+ HsDev.Tools.Ghc.Worker: instance Text.Format.Formattable HsDev.Tools.Ghc.Worker.SessionConfig
+ HsDev.Tools.Ghc.Worker: interpretedFlags :: DynFlags -> DynFlags
+ HsDev.Tools.Ghc.Worker: loadInteractive :: GhcMonad m => Path -> Maybe Text -> m ()
+ HsDev.Tools.Ghc.Worker: noLinkFlags :: DynFlags -> DynFlags
+ HsDev.Tools.Ghc.Worker: reload :: GhcMonad m => m ()
+ HsDev.Tools.Ghc.Worker: tmpSession :: [String] -> GhcM ()
+ HsDev.Tools.HDocs: hdocsPackage :: PackageConfig -> GhcM (Map Text (Map Text Text))
+ HsDev.Tools.HDocs: hdocsSupported :: Bool
+ HsDev.Tools.HDocs: readDocs :: Text -> [String] -> Path -> GhcM (Maybe (Map String String))
+ HsDev.Tools.HDocs: readModuleDocs :: [String] -> Module -> GhcM (Maybe (Map String String))
+ HsDev.Tools.HDocs: readProjectTargetDocs :: [String] -> Project -> [Path] -> GhcM (Map String (Map String String))
+ HsDev.Tools.HDocs: setModuleDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text (Map Text Text) -> Module -> m Module
+ HsDev.Tools.HDocs: setSymbolDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text Text -> Symbol -> m Symbol
+ HsDev.Tools.Hayoo: hayooAsSymbol :: HayooSymbol -> Maybe Symbol
+ HsDev.Tools.Hayoo: instance HsDev.Symbols.Class.Sourced HsDev.Tools.Hayoo.HayooSymbol
+ HsDev.Tools.Refact: Refact :: Text -> Replace Text -> Refact
+ HsDev.Tools.Refact: [_refactAction] :: Refact -> Replace Text
+ HsDev.Tools.Refact: [_refactMessage] :: Refact -> Text
+ HsDev.Tools.Refact: cut :: EditAction e s => Region -> e s
+ HsDev.Tools.Refact: data Refact
+ HsDev.Tools.Refact: fromPosition :: Position -> Point
+ HsDev.Tools.Refact: fromRegion :: Region -> Region
+ HsDev.Tools.Refact: instance Data.Aeson.Types.FromJSON.FromJSON HsDev.Tools.Refact.Refact
+ HsDev.Tools.Refact: instance Data.Aeson.Types.ToJSON.ToJSON HsDev.Tools.Refact.Refact
+ HsDev.Tools.Refact: instance Data.Text.Region.Types.Regioned HsDev.Tools.Refact.Refact
+ HsDev.Tools.Refact: instance GHC.Classes.Eq HsDev.Tools.Refact.Refact
+ HsDev.Tools.Refact: instance GHC.Show.Show HsDev.Tools.Refact.Refact
+ HsDev.Tools.Refact: paste :: EditAction e s => Point -> s -> e s
+ HsDev.Tools.Refact: refact :: [Refact] -> Text -> Text
+ HsDev.Tools.Refact: refactAction :: Lens' Refact (Replace Text)
+ HsDev.Tools.Refact: refactMessage :: Lens' Refact Text
+ HsDev.Tools.Refact: replace :: EditAction e s => Region -> s -> e s
+ HsDev.Tools.Refact: update :: Regioned a => [Refact] -> [a] -> [a]
+ HsDev.Types: SQLiteError :: String -> HsDevError
+ HsDev.Types: UnhandledError :: String -> HsDevError
+ HsDev.Util: fromJSON' :: FromJSON a => Value -> Maybe a
+ HsDev.Util: logAll :: (MonadLog m, MonadCatch m) => m () -> m ()
+ HsDev.Util: parseDT :: (Monad m, Text a) => String -> String -> m a
+ System.Directory.Paths: dirExists :: Path -> IO Bool
+ System.Directory.Paths: fileExists :: Path -> IO Bool
+ System.Directory.Paths: fromFilePath :: FilePath -> Path
+ System.Directory.Paths: instance System.Directory.Paths.Paths System.Directory.Paths.Path
+ System.Directory.Paths: isParent :: Path -> Path -> Bool
+ System.Directory.Paths: joinPaths :: [Path] -> Path
+ System.Directory.Paths: normPath :: Path -> Path
+ System.Directory.Paths: path :: Lens' Path FilePath
+ System.Directory.Paths: relPathTo :: Path -> Path -> Path
+ System.Directory.Paths: splitPaths :: Path -> [Path]
+ System.Directory.Paths: subPath :: Path -> Path -> Path
+ System.Directory.Paths: takeDir :: Path -> Path
+ System.Directory.Paths: type Path = Text
+ System.Directory.Watcher: eventGroup :: Watcher a -> NominalDiffTime -> ([(a, Event)] -> IO ()) -> IO ()
+ System.Directory.Watcher: instance GHC.Classes.Eq System.Directory.Watcher.Event
+ System.Directory.Watcher: instance GHC.Classes.Ord System.Directory.Watcher.Event
+ System.Directory.Watcher: instance GHC.Show.Show System.Directory.Watcher.Event
+ System.Directory.Watcher: onEvents :: Watcher a -> NominalDiffTime -> ([(a, Event)] -> IO ()) -> IO ()
+ System.Directory.Watcher: onEvents_ :: Watcher a -> ([(a, Event)] -> IO ()) -> IO ()
- Control.Concurrent.Worker: Worker :: Chan (Async (), m ()) -> (forall a. m a -> m a) -> MVar (Async ()) -> IO () -> Worker m
+ Control.Concurrent.Worker: Worker :: Chan (Async (), m ()) -> MVar (Async ()) -> Worker m
- Control.Concurrent.Worker: startWorker :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> (forall a. m a -> m a) -> IO (Worker m)
+ Control.Concurrent.Worker: startWorker :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> (m () -> m ()) -> IO (Worker m)
- Data.Deps: flatten :: Ord a => Deps a -> Deps a
+ Data.Deps: flatten :: Ord a => Deps a -> Either (DepsError a) (Deps a)
- Data.Help: brief :: Help a => a -> String
+ Data.Help: brief :: Help a => a -> Text
- Data.Help: detailed :: Help a => a -> [String]
+ Data.Help: detailed :: Help a => a -> [Text]
- Data.Help: help :: Help a => a -> [String]
+ Data.Help: help :: Help a => a -> [Text]
- Data.Help: indent :: String -> String
+ Data.Help: indent :: Text -> Text
- Data.Help: indentHelp :: [String] -> [String]
+ Data.Help: indentHelp :: [Text] -> [Text]
- Data.Help: indented :: Help a => a -> [String]
+ Data.Help: indented :: Help a => a -> [Text]
- HsDev.Database.Update: UpdateM :: ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m)) a -> UpdateM m a
+ HsDev.Database.Update: UpdateM :: ReaderT UpdateState (WriterT [ModuleLocation] (ClientM m)) a -> UpdateM m a
- HsDev.Database.Update: [runUpdateM] :: UpdateM m a -> ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m)) a
+ HsDev.Database.Update: [runUpdateM] :: UpdateM m a -> ReaderT UpdateState (WriterT [ModuleLocation] (ClientM m)) a
- HsDev.Database.Update: inferModTypes :: UpdateMonad m => [InspectedModule] -> m ()
+ HsDev.Database.Update: inferModTypes :: UpdateMonad m => [Module] -> m ()
- HsDev.Database.Update: runTasks :: UpdateMonad m => [m ()] -> m ()
+ HsDev.Database.Update: runTasks :: UpdateMonad m => [m a] -> m [a]
- HsDev.Database.Update: scan :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> (Database -> Database) -> [ModuleToScan] -> [String] -> ([ModuleToScan] -> m ()) -> m ()
+ HsDev.Database.Update: scan :: UpdateMonad m => m [(Only Int) :. (ModuleLocation :. Inspection)] -> [ModuleToScan] -> [String] -> ([ModuleToScan] -> m ()) -> m ()
- HsDev.Database.Update: scanDirectory :: UpdateMonad m => [String] -> FilePath -> m ()
+ HsDev.Database.Update: scanDirectory :: UpdateMonad m => [String] -> Path -> m ()
- HsDev.Database.Update: scanDocs :: UpdateMonad m => [InspectedModule] -> m ()
+ HsDev.Database.Update: scanDocs :: UpdateMonad m => [Module] -> m ()
- HsDev.Database.Update: scanFile :: UpdateMonad m => [String] -> FilePath -> m ()
+ HsDev.Database.Update: scanFile :: UpdateMonad m => [String] -> Path -> m ()
- HsDev.Database.Update: scanFileContents :: UpdateMonad m => [String] -> FilePath -> Maybe String -> m ()
+ HsDev.Database.Update: scanFileContents :: UpdateMonad m => [String] -> Path -> Maybe Text -> m ()
- HsDev.Database.Update: scanProject :: UpdateMonad m => [String] -> FilePath -> m ()
+ HsDev.Database.Update: scanProject :: UpdateMonad m => [String] -> Path -> m ()
- HsDev.Database.Update: scanProjectFile :: UpdateMonad m => [String] -> FilePath -> m Project
+ HsDev.Database.Update: scanProjectFile :: UpdateMonad m => [String] -> Path -> m Project
- HsDev.Database.Update: scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m ()
+ HsDev.Database.Update: scanProjectStack :: UpdateMonad m => [String] -> Path -> m ()
- HsDev.Database.Update: updater :: UpdateMonad m => Database -> m ()
+ HsDev.Database.Update: updater :: UpdateMonad m => [ModuleLocation] -> m ()
- HsDev.Database.Update.Types: UpdateM :: ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m)) a -> UpdateM m a
+ HsDev.Database.Update.Types: UpdateM :: ReaderT UpdateState (WriterT [ModuleLocation] (ClientM m)) a -> UpdateM m a
- HsDev.Database.Update.Types: [runUpdateM] :: UpdateM m a -> ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m)) a
+ HsDev.Database.Update.Types: [runUpdateM] :: UpdateM m a -> ReaderT UpdateState (WriterT [ModuleLocation] (ClientM m)) a
- HsDev.Database.Update.Types: type UpdateMonad m = (CommandMonad m, MonadReader UpdateOptions m, MonadWriter [ModuleLocation] m)
+ HsDev.Database.Update.Types: type UpdateMonad m = (CommandMonad m, MonadReader UpdateState m, MonadWriter [ModuleLocation] m)
- HsDev.Inspect: contentsInspection :: String -> [String] -> ExceptT String IO Inspection
+ HsDev.Inspect: contentsInspection :: Text -> [String] -> ExceptT String IO Inspection
- HsDev.Inspect: fileInspection :: FilePath -> [String] -> IO Inspection
+ HsDev.Inspect: fileInspection :: Path -> [String] -> IO Inspection
- HsDev.Inspect: inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule
+ HsDev.Inspect: inspectContents :: Text -> [(String, String)] -> [String] -> Text -> ExceptT String IO InspectedModule
- HsDev.Inspect: inspectDocs :: [String] -> Module -> IO Module
+ HsDev.Inspect: inspectDocs :: [String] -> Module -> GhcM Module
- HsDev.Inspect: inspectDocsGhc :: [String] -> Module -> Ghc Module
+ HsDev.Inspect: inspectDocsGhc :: [String] -> Module -> GhcM Module
- HsDev.Inspect: inspectFile :: [(String, String)] -> [String] -> FilePath -> Maybe String -> IO InspectedModule
+ HsDev.Inspect: inspectFile :: [(String, String)] -> [String] -> Path -> Maybe Project -> Maybe Text -> IO InspectedModule
- HsDev.Inspect: preprocess :: [(String, String)] -> FilePath -> String -> IO String
+ HsDev.Inspect: preprocess :: [(String, String)] -> Path -> Text -> IO Text
- HsDev.Inspect: preprocess_ :: [(String, String)] -> [String] -> FilePath -> String -> IO String
+ HsDev.Inspect: preprocess_ :: [(String, String)] -> [String] -> Path -> Text -> IO Text
- HsDev.Inspect: projectDirs :: Project -> IO [Extensions FilePath]
+ HsDev.Inspect: projectDirs :: Project -> IO [Extensions Path]
- HsDev.Inspect: projectSources :: Project -> IO [Extensions FilePath]
+ HsDev.Inspect: projectSources :: Project -> IO [Extensions Path]
- HsDev.Project: fileTarget :: Project -> FilePath -> Maybe Info
+ HsDev.Project: fileTarget :: Project -> Path -> Maybe Info
- HsDev.Project: fileTargets :: Project -> FilePath -> [Info]
+ HsDev.Project: fileTargets :: Project -> Path -> [Info]
- HsDev.Project: findSourceDir :: Project -> FilePath -> Maybe (Extensions FilePath)
+ HsDev.Project: findSourceDir :: Project -> Path -> Maybe (Extensions Path)
- HsDev.Project: infoSourceDirsDef :: Simple Lens Info [FilePath]
+ HsDev.Project: infoSourceDirsDef :: Lens' Info [Path]
- HsDev.Project: sourceDirs :: ProjectDescription -> [Extensions FilePath]
+ HsDev.Project: sourceDirs :: ProjectDescription -> [Extensions Path]
- HsDev.Project.Compat: componentName :: String -> String
+ HsDev.Project.Compat: componentName :: UnqualComponentName -> Text
- HsDev.Project.Types: Executable :: String -> FilePath -> Info -> Executable
+ HsDev.Project.Types: Executable :: Text -> Path -> Info -> Executable
- HsDev.Project.Types: Extensions :: [Extension] -> [String] -> a -> Extensions a
+ HsDev.Project.Types: Extensions :: [Extension] -> [Text] -> a -> Extensions a
- HsDev.Project.Types: Info :: [String] -> Maybe Language -> [Extension] -> [String] -> [FilePath] -> Info
+ HsDev.Project.Types: Info :: [Text] -> Maybe Language -> [Extension] -> [Text] -> [Path] -> [[Text]] -> Info
- HsDev.Project.Types: Library :: [[String]] -> Info -> Library
+ HsDev.Project.Types: Library :: [[Text]] -> Info -> Library
- HsDev.Project.Types: Project :: String -> FilePath -> FilePath -> Maybe ProjectDescription -> Project
+ HsDev.Project.Types: Project :: Text -> Path -> Path -> Maybe ProjectDescription -> Project
- HsDev.Project.Types: ProjectDescription :: String -> Maybe Library -> [Executable] -> [Test] -> ProjectDescription
+ HsDev.Project.Types: ProjectDescription :: Text -> Maybe Library -> [Executable] -> [Test] -> ProjectDescription
- HsDev.Project.Types: Test :: String -> Bool -> Info -> Test
+ HsDev.Project.Types: Test :: Text -> Bool -> Maybe Path -> Info -> Test
- HsDev.Project.Types: [_executableName] :: Executable -> String
+ HsDev.Project.Types: [_executableName] :: Executable -> Text
- HsDev.Project.Types: [_executablePath] :: Executable -> FilePath
+ HsDev.Project.Types: [_executablePath] :: Executable -> Path
- HsDev.Project.Types: [_ghcOptions] :: Extensions a -> [String]
+ HsDev.Project.Types: [_ghcOptions] :: Extensions a -> [Text]
- HsDev.Project.Types: [_infoDepends] :: Info -> [String]
+ HsDev.Project.Types: [_infoDepends] :: Info -> [Text]
- HsDev.Project.Types: [_infoGHCOptions] :: Info -> [String]
+ HsDev.Project.Types: [_infoGHCOptions] :: Info -> [Text]
- HsDev.Project.Types: [_infoSourceDirs] :: Info -> [FilePath]
+ HsDev.Project.Types: [_infoSourceDirs] :: Info -> [Path]
- HsDev.Project.Types: [_libraryModules] :: Library -> [[String]]
+ HsDev.Project.Types: [_libraryModules] :: Library -> [[Text]]
- HsDev.Project.Types: [_projectCabal] :: Project -> FilePath
+ HsDev.Project.Types: [_projectCabal] :: Project -> Path
- HsDev.Project.Types: [_projectName] :: Project -> String
+ HsDev.Project.Types: [_projectName] :: Project -> Text
- HsDev.Project.Types: [_projectPath] :: Project -> FilePath
+ HsDev.Project.Types: [_projectPath] :: Project -> Path
- HsDev.Project.Types: [_projectVersion] :: ProjectDescription -> String
+ HsDev.Project.Types: [_projectVersion] :: ProjectDescription -> Text
- HsDev.Project.Types: [_testName] :: Test -> String
+ HsDev.Project.Types: [_testName] :: Test -> Text
- HsDev.Project.Types: buildInfo :: Target a => a -> Info
+ HsDev.Project.Types: buildInfo :: Target a => Lens' a Info
- HsDev.Project.Types: entity :: forall a_a14TT a_a16Va. Lens (Extensions a_a14TT) (Extensions a_a16Va) a_a14TT a_a16Va
+ HsDev.Project.Types: entity :: forall a_a1hkw a_a1iMr. Lens (Extensions a_a1hkw) (Extensions a_a1iMr) a_a1hkw a_a1iMr
- HsDev.Project.Types: executableName :: Lens' Executable String
+ HsDev.Project.Types: executableName :: Lens' Executable Text
- HsDev.Project.Types: executablePath :: Lens' Executable FilePath
+ HsDev.Project.Types: executablePath :: Lens' Executable Path
- HsDev.Project.Types: extensions :: forall a_a14TT. Lens' (Extensions a_a14TT) [Extension]
+ HsDev.Project.Types: extensions :: forall a_a1hkw. Lens' (Extensions a_a1hkw) [Extension]
- HsDev.Project.Types: ghcOptions :: forall a_a14TT. Lens' (Extensions a_a14TT) [String]
+ HsDev.Project.Types: ghcOptions :: forall a_a1hkw. Lens' (Extensions a_a1hkw) [Text]
- HsDev.Project.Types: infoDepends :: Lens' Info [String]
+ HsDev.Project.Types: infoDepends :: Lens' Info [Text]
- HsDev.Project.Types: infoGHCOptions :: Lens' Info [String]
+ HsDev.Project.Types: infoGHCOptions :: Lens' Info [Text]
- HsDev.Project.Types: infoSourceDirs :: Lens' Info [FilePath]
+ HsDev.Project.Types: infoSourceDirs :: Lens' Info [Path]
- HsDev.Project.Types: libraryModules :: Lens' Library [[String]]
+ HsDev.Project.Types: libraryModules :: Lens' Library [[Text]]
- HsDev.Project.Types: projectCabal :: Lens' Project FilePath
+ HsDev.Project.Types: projectCabal :: Lens' Project Path
- HsDev.Project.Types: projectName :: Lens' Project String
+ HsDev.Project.Types: projectName :: Lens' Project Text
- HsDev.Project.Types: projectPath :: Lens' Project FilePath
+ HsDev.Project.Types: projectPath :: Lens' Project Path
- HsDev.Project.Types: projectVersion :: Lens' ProjectDescription String
+ HsDev.Project.Types: projectVersion :: Lens' ProjectDescription Text
- HsDev.Project.Types: testName :: Lens' Test String
+ HsDev.Project.Types: testName :: Lens' Test Text
- HsDev.Sandbox: Sandbox :: SandboxType -> FilePath -> Sandbox
+ HsDev.Sandbox: Sandbox :: SandboxType -> Path -> Sandbox
- HsDev.Sandbox: [_sandbox] :: Sandbox -> FilePath
+ HsDev.Sandbox: [_sandbox] :: Sandbox -> Path
- HsDev.Sandbox: cabalSandboxLib :: MonadLog m => m FilePath
+ HsDev.Sandbox: cabalSandboxLib :: GhcM FilePath
- HsDev.Sandbox: cabalSandboxPackageDb :: MonadLog m => m FilePath
+ HsDev.Sandbox: cabalSandboxPackageDb :: GhcM FilePath
- HsDev.Sandbox: findSandbox :: FilePath -> IO (Maybe Sandbox)
+ HsDev.Sandbox: findSandbox :: Path -> IO (Maybe Sandbox)
- HsDev.Sandbox: getModuleOpts :: MonadLog m => [String] -> Module -> m [String]
+ HsDev.Sandbox: getModuleOpts :: [String] -> Module -> GhcM [String]
- HsDev.Sandbox: guessSandboxType :: FilePath -> Maybe SandboxType
+ HsDev.Sandbox: guessSandboxType :: Path -> Maybe SandboxType
- HsDev.Sandbox: isSandbox :: FilePath -> Bool
+ HsDev.Sandbox: isSandbox :: Path -> Bool
- HsDev.Sandbox: projectSandbox :: FilePath -> IO (Maybe Sandbox)
+ HsDev.Sandbox: projectSandbox :: Path -> IO (Maybe Sandbox)
- HsDev.Sandbox: restorePackageDbStack :: MonadLog m => PackageDb -> m PackageDbStack
+ HsDev.Sandbox: restorePackageDbStack :: PackageDb -> GhcM PackageDbStack
- HsDev.Sandbox: sandbox :: Lens' Sandbox FilePath
+ HsDev.Sandbox: sandbox :: Lens' Sandbox Path
- HsDev.Sandbox: sandboxFromPath :: FilePath -> Maybe Sandbox
+ HsDev.Sandbox: sandboxFromPath :: Path -> Maybe Sandbox
- HsDev.Sandbox: sandboxPackageDbStack :: MonadLog m => Sandbox -> m PackageDbStack
+ HsDev.Sandbox: sandboxPackageDbStack :: Sandbox -> GhcM PackageDbStack
- HsDev.Sandbox: searchPackageDbStack :: MonadLog m => FilePath -> m PackageDbStack
+ HsDev.Sandbox: searchPackageDbStack :: Path -> GhcM PackageDbStack
- HsDev.Sandbox: searchSandbox :: FilePath -> IO (Maybe Sandbox)
+ HsDev.Sandbox: searchSandbox :: Path -> IO (Maybe Sandbox)
- HsDev.Scan: changedModules :: Database -> [String] -> [ModuleToScan] -> IO [ModuleToScan]
+ HsDev.Scan: changedModules :: SessionMonad m => Map ModuleLocation Inspection -> [String] -> [ModuleToScan] -> m [ModuleToScan]
- HsDev.Scan: scanModify :: CommandMonad m => ([String] -> PackageDbStack -> Module -> m Module) -> InspectedModule -> m InspectedModule
+ HsDev.Scan: scanModify :: CommandMonad m => ([String] -> Module -> m Module) -> InspectedModule -> m InspectedModule
- HsDev.Scan: scanProjectFile :: CommandMonad m => [String] -> FilePath -> m Project
+ HsDev.Scan: scanProjectFile :: CommandMonad m => [String] -> Path -> m Project
- HsDev.Scan: type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe String)
+ HsDev.Scan: type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe Text)
- HsDev.Scan: upToDate :: [String] -> InspectedModule -> IO Bool
+ HsDev.Scan: upToDate :: SessionMonad m => ModuleLocation -> [String] -> Inspection -> m Bool
- HsDev.Scan.Browse: browseModules :: MonadLog m => [String] -> PackageDbStack -> [ModuleLocation] -> m [InspectedModule]
+ HsDev.Scan.Browse: browseModules :: [String] -> PackageDbStack -> [ModuleLocation] -> GhcM [InspectedModule]
- HsDev.Scan.Browse: browsePackages :: MonadLog m => [String] -> PackageDbStack -> m [PackageConfig]
+ HsDev.Scan.Browse: browsePackages :: [String] -> PackageDbStack -> GhcM [PackageConfig]
- HsDev.Scan.Browse: browsePackagesDeps :: MonadLog m => [String] -> PackageDbStack -> m (Deps PackageConfig)
+ HsDev.Scan.Browse: browsePackagesDeps :: [String] -> PackageDbStack -> GhcM (Deps PackageConfig)
- HsDev.Scan.Browse: ghcModuleLocation :: PackageDb -> PackageConfig -> Module -> ModuleLocation
+ HsDev.Scan.Browse: ghcModuleLocation :: PackageConfig -> Module -> ModuleLocation
- HsDev.Scan.Browse: listModules :: MonadLog m => [String] -> PackageDbStack -> m [ModuleLocation]
+ HsDev.Scan.Browse: listModules :: [String] -> PackageDbStack -> [ModulePackage] -> GhcM [ModuleLocation]
- HsDev.Server.Base: inServer :: Server -> CommandOptions -> Command -> IO Result
+ HsDev.Server.Base: inServer :: Server -> ServerM IO a -> IO a
- HsDev.Server.Commands: ServerOpts :: ConnectionPort -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> Bool -> ServerOpts
+ HsDev.Server.Commands: ServerOpts :: ConnectionPort -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> ServerOpts
- HsDev.Server.Message: message :: forall a_a1CSm a_a1D0r. Lens (Message a_a1CSm) (Message a_a1D0r) a_a1CSm a_a1D0r
+ HsDev.Server.Message: message :: forall a_a1IqJ a_a1Iw8. Lens (Message a_a1IqJ) (Message a_a1Iw8) a_a1IqJ a_a1Iw8
- HsDev.Server.Message: messageId :: forall a_a1CSm. Lens' (Message a_a1CSm) (Maybe String)
+ HsDev.Server.Message: messageId :: forall a_a1IqJ. Lens' (Message a_a1IqJ) (Maybe String)
- HsDev.Server.Types: AutoFix :: AutoFixCommand -> Command
+ HsDev.Server.Types: AutoFix :: [Note OutputMessage] -> Command
- HsDev.Server.Types: CabalList :: [String] -> Command
+ HsDev.Server.Types: CabalList :: [Text] -> Command
- HsDev.Server.Types: Check :: [FileSource] -> [String] -> Command
+ HsDev.Server.Types: Check :: [FileSource] -> [String] -> Bool -> Command
- HsDev.Server.Types: CheckLint :: [FileSource] -> [String] -> Command
+ HsDev.Server.Types: CheckLint :: [FileSource] -> [String] -> Bool -> Command
- HsDev.Server.Types: Complete :: String -> Bool -> FilePath -> Command
+ HsDev.Server.Types: Complete :: Text -> Bool -> Path -> Command
- HsDev.Server.Types: FileSource :: FilePath -> Maybe String -> FileSource
+ HsDev.Server.Types: FileSource :: Path -> Maybe Text -> FileSource
- HsDev.Server.Types: InferTypes :: [FilePath] -> [FilePath] -> [String] -> Command
+ HsDev.Server.Types: InferTypes :: [Path] -> [Path] -> Command
- HsDev.Server.Types: InfoModule :: SearchQuery -> [TargetFilter] -> Command
+ HsDev.Server.Types: InfoModule :: SearchQuery -> [TargetFilter] -> Bool -> Bool -> Command
- HsDev.Server.Types: InfoProject :: (Either String FilePath) -> Command
+ HsDev.Server.Types: InfoProject :: (Either Text Path) -> Command
- HsDev.Server.Types: InfoSandbox :: FilePath -> Command
+ HsDev.Server.Types: InfoSandbox :: Path -> Command
- HsDev.Server.Types: InfoSymbol :: SearchQuery -> [TargetFilter] -> Bool -> Command
+ HsDev.Server.Types: InfoSymbol :: SearchQuery -> [TargetFilter] -> Bool -> Bool -> Command
- HsDev.Server.Types: Lookup :: String -> FilePath -> Command
+ HsDev.Server.Types: Lookup :: Text -> Path -> Command
- HsDev.Server.Types: RefineDocs :: [FilePath] -> [FilePath] -> [String] -> Command
+ HsDev.Server.Types: RefineDocs :: [Path] -> [Path] -> Command
- HsDev.Server.Types: Remove :: [FilePath] -> Bool -> [FilePath] -> [FilePath] -> Command
+ HsDev.Server.Types: Remove :: [Path] -> Bool -> [Path] -> [Path] -> Command
- HsDev.Server.Types: ResolveScope :: SearchQuery -> Bool -> FilePath -> Command
+ HsDev.Server.Types: ResolveScope :: SearchQuery -> Path -> Command
- HsDev.Server.Types: ResolveScopeModules :: SearchQuery -> FilePath -> Command
+ HsDev.Server.Types: ResolveScopeModules :: SearchQuery -> Path -> Command
- HsDev.Server.Types: Scan :: [FilePath] -> Bool -> [FilePath] -> [FileSource] -> [FilePath] -> [String] -> Bool -> Bool -> Command
+ HsDev.Server.Types: Scan :: [Path] -> Bool -> [Path] -> [FileSource] -> [Path] -> [String] -> Bool -> Bool -> Command
- HsDev.Server.Types: SearchQuery :: String -> SearchType -> SearchQuery
+ HsDev.Server.Types: SearchQuery :: Text -> SearchType -> SearchQuery
- HsDev.Server.Types: ServerOpts :: ConnectionPort -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> Bool -> ServerOpts
+ HsDev.Server.Types: ServerOpts :: ConnectionPort -> Int -> Maybe FilePath -> String -> Maybe FilePath -> Bool -> ServerOpts
- HsDev.Server.Types: Session :: Async Database -> (Database -> ServerM IO ()) -> ((FilePath -> ExceptT String IO Structured) -> ServerM IO (Maybe Database)) -> SessionLog -> Watcher -> GhcWorker -> IO () -> IO () -> [(String, String)] -> Session
+ HsDev.Server.Types: Session :: Connection -> String -> SessionLog -> Watcher -> (Path -> Maybe Text -> IO ()) -> GhcWorker -> Worker (ServerM IO) -> IO () -> IO () -> Chan (IO ()) -> [(String, String)] -> Session
- HsDev.Server.Types: SessionLog :: Log -> IO [String] -> IO () -> SessionLog
+ HsDev.Server.Types: SessionLog :: Log -> IO [Message] -> IO () -> SessionLog
- HsDev.Server.Types: TargetFile :: FilePath -> TargetFilter
+ HsDev.Server.Types: TargetFile :: Path -> TargetFilter
- HsDev.Server.Types: TargetModule :: String -> TargetFilter
+ HsDev.Server.Types: TargetModule :: Text -> TargetFilter
- HsDev.Server.Types: TargetPackage :: String -> TargetFilter
+ HsDev.Server.Types: TargetPackage :: Text -> TargetFilter
- HsDev.Server.Types: TargetProject :: String -> TargetFilter
+ HsDev.Server.Types: TargetProject :: Text -> TargetFilter
- HsDev.Server.Types: Types :: [FileSource] -> [String] -> Command
+ HsDev.Server.Types: Types :: [FileSource] -> [String] -> Bool -> Command
- HsDev.Server.Types: Whois :: String -> FilePath -> Command
+ HsDev.Server.Types: Whois :: Text -> Path -> Command
- HsDev.Server.Types: [cabalListPackages] :: Command -> [String]
+ HsDev.Server.Types: [cabalListPackages] :: Command -> [Text]
- HsDev.Server.Types: [docsFiles] :: Command -> [FilePath]
+ HsDev.Server.Types: [docsFiles] :: Command -> [Path]
- HsDev.Server.Types: [docsProjects] :: Command -> [FilePath]
+ HsDev.Server.Types: [docsProjects] :: Command -> [Path]
- HsDev.Server.Types: [fileContents] :: FileSource -> Maybe String
+ HsDev.Server.Types: [fileContents] :: FileSource -> Maybe Text
- HsDev.Server.Types: [fileSource] :: FileSource -> FilePath
+ HsDev.Server.Types: [fileSource] :: FileSource -> Path
- HsDev.Server.Types: [inferFiles] :: Command -> [FilePath]
+ HsDev.Server.Types: [inferFiles] :: Command -> [Path]
- HsDev.Server.Types: [inferProjects] :: Command -> [FilePath]
+ HsDev.Server.Types: [inferProjects] :: Command -> [Path]
- HsDev.Server.Types: [removeFiles] :: Command -> [FilePath]
+ HsDev.Server.Types: [removeFiles] :: Command -> [Path]
- HsDev.Server.Types: [removeProjects] :: Command -> [FilePath]
+ HsDev.Server.Types: [removeProjects] :: Command -> [Path]
- HsDev.Server.Types: [removeSandboxes] :: Command -> [FilePath]
+ HsDev.Server.Types: [removeSandboxes] :: Command -> [Path]
- HsDev.Server.Types: [scanPaths] :: Command -> [FilePath]
+ HsDev.Server.Types: [scanPaths] :: Command -> [Path]
- HsDev.Server.Types: [scanProjects] :: Command -> [FilePath]
+ HsDev.Server.Types: [scanProjects] :: Command -> [Path]
- HsDev.Server.Types: [scanSandboxes] :: Command -> [FilePath]
+ HsDev.Server.Types: [scanSandboxes] :: Command -> [Path]
- HsDev.Server.Types: [sessionListenLog] :: SessionLog -> IO [String]
+ HsDev.Server.Types: [sessionListenLog] :: SessionLog -> IO [Message]
- HsDev.Server.Types: serverListen :: SessionMonad m => m [String]
+ HsDev.Server.Types: serverListen :: SessionMonad m => m [Message]
- HsDev.Stack: MaybeT :: m (Maybe a) -> MaybeT a
+ HsDev.Stack: MaybeT :: m Maybe a -> MaybeT a
- HsDev.Stack: [runMaybeT] :: MaybeT a -> m (Maybe a)
+ HsDev.Stack: [runMaybeT] :: MaybeT a -> m Maybe a
- HsDev.Stack: build :: MonadLog m => [String] -> Maybe FilePath -> m ()
+ HsDev.Stack: build :: [String] -> Maybe FilePath -> GhcM ()
- HsDev.Stack: buildDeps :: MonadLog m => Maybe FilePath -> m ()
+ HsDev.Stack: buildDeps :: Maybe FilePath -> GhcM ()
- HsDev.Stack: configure :: MonadLog m => Maybe FilePath -> m ()
+ HsDev.Stack: configure :: Maybe FilePath -> GhcM ()
- HsDev.Stack: getStackEnv :: Paths -> Maybe StackEnv
+ HsDev.Stack: getStackEnv :: PathsConf -> Maybe StackEnv
- HsDev.Stack: path :: MonadLog m => Maybe FilePath -> m Paths
+ HsDev.Stack: path :: Maybe FilePath -> GhcM PathsConf
- HsDev.Stack: pathOf :: String -> Lens' Paths (Maybe FilePath)
+ HsDev.Stack: pathOf :: String -> Lens' PathsConf (Maybe FilePath)
- HsDev.Stack: projectEnv :: MonadLog m => FilePath -> m StackEnv
+ HsDev.Stack: projectEnv :: FilePath -> GhcM StackEnv
- HsDev.Stack: stack :: MonadLog m => [String] -> m String
+ HsDev.Stack: stack :: [String] -> GhcM String
- HsDev.Stack: stackCompiler :: MonadLog m => m String
+ HsDev.Stack: stackCompiler :: GhcM String
- HsDev.Symbols: locateSourceDir :: FilePath -> IO (Maybe (Extensions FilePath))
+ HsDev.Symbols: locateSourceDir :: FilePath -> IO (Maybe (Extensions Path))
- HsDev.Symbols.Documented: brief :: Documented a => a -> String
+ HsDev.Symbols.Documented: brief :: Documented a => a -> Text
- HsDev.Symbols.Documented: class Symbol a => Documented a where detailed = unlines . defaultDetailed
+ HsDev.Symbols.Documented: class Documented a
- HsDev.Symbols.Documented: defaultDetailed :: Documented a => a -> [String]
+ HsDev.Symbols.Documented: defaultDetailed :: (Sourced a, Documented a) => a -> [Text]
- HsDev.Symbols.Documented: detailed :: Documented a => a -> String
+ HsDev.Symbols.Documented: detailed :: (Documented a, Sourced a) => a -> Text
- HsDev.Symbols.Location: FileModule :: FilePath -> Maybe Project -> ModuleLocation
+ HsDev.Symbols.Location: FileModule :: Path -> Maybe Project -> ModuleLocation
- HsDev.Symbols.Location: InstalledModule :: PackageDb -> Maybe ModulePackage -> String -> ModuleLocation
+ HsDev.Symbols.Location: InstalledModule :: [Path] -> ModulePackage -> Text -> ModuleLocation
- HsDev.Symbols.Location: ModulePackage :: String -> String -> ModulePackage
+ HsDev.Symbols.Location: ModulePackage :: Text -> Text -> ModulePackage
- HsDev.Symbols.Location: [_moduleFile] :: ModuleLocation -> FilePath
+ HsDev.Symbols.Location: [_moduleFile] :: ModuleLocation -> Path
- HsDev.Symbols.Location: [_modulePackage] :: ModuleLocation -> Maybe ModulePackage
+ HsDev.Symbols.Location: [_modulePackage] :: ModuleLocation -> ModulePackage
- HsDev.Symbols.Location: [_packageName] :: ModulePackage -> String
+ HsDev.Symbols.Location: [_packageName] :: ModulePackage -> Text
- HsDev.Symbols.Location: [_packageVersion] :: ModulePackage -> String
+ HsDev.Symbols.Location: [_packageVersion] :: ModulePackage -> Text
- HsDev.Symbols.Location: calcTabs :: RecalcTabs a => String -> Int -> a -> a
+ HsDev.Symbols.Location: calcTabs :: RecalcTabs a => Text -> Int -> a -> a
- HsDev.Symbols.Location: locationId :: ModuleLocation -> String
+ HsDev.Symbols.Location: locationId :: ModuleLocation -> Text
- HsDev.Symbols.Location: moduleFile :: Traversal' ModuleLocation FilePath
+ HsDev.Symbols.Location: moduleFile :: Traversal' ModuleLocation Path
- HsDev.Symbols.Location: modulePackage :: Traversal' ModuleLocation (Maybe ModulePackage)
+ HsDev.Symbols.Location: modulePackage :: Traversal' ModuleLocation ModulePackage
- HsDev.Symbols.Location: packageName :: Lens' ModulePackage String
+ HsDev.Symbols.Location: packageName :: Lens' ModulePackage Text
- HsDev.Symbols.Location: packageVersion :: Lens' ModulePackage String
+ HsDev.Symbols.Location: packageVersion :: Lens' ModulePackage Text
- HsDev.Symbols.Location: recalcTabs :: RecalcTabs a => String -> Int -> a -> a
+ HsDev.Symbols.Location: recalcTabs :: RecalcTabs a => Text -> Int -> a -> a
- HsDev.Symbols.Location: regionStr :: Region -> String -> String
+ HsDev.Symbols.Location: regionStr :: Region -> Text -> Text
- HsDev.Symbols.Location: sourceModuleRoot :: Text -> FilePath -> FilePath
+ HsDev.Symbols.Location: sourceModuleRoot :: Text -> Path -> Path
- HsDev.Symbols.Types: Class :: TypeInfo -> DeclarationInfo
+ HsDev.Symbols.Types: Class :: [Text] -> [Text] -> SymbolInfo
- HsDev.Symbols.Types: Data :: TypeInfo -> DeclarationInfo
+ HsDev.Symbols.Types: Data :: [Text] -> [Text] -> SymbolInfo
- HsDev.Symbols.Types: Function :: Maybe Text -> [Declaration] -> Maybe Text -> DeclarationInfo
+ HsDev.Symbols.Types: Function :: Maybe Text -> SymbolInfo
- HsDev.Symbols.Types: Inspected :: Inspection -> i -> Set t -> Either HsDevError a -> Inspected i t a
+ HsDev.Symbols.Types: Inspected :: Inspection -> k -> Set t -> Either HsDevError a -> Inspected k t a
- HsDev.Symbols.Types: InspectionAt :: POSIXTime -> [String] -> Inspection
+ HsDev.Symbols.Types: InspectionAt :: POSIXTime -> [Text] -> Inspection
- HsDev.Symbols.Types: Module :: Text -> Maybe Text -> ModuleLocation -> Maybe [Export] -> [Import] -> [Declaration] -> Module
+ HsDev.Symbols.Types: Module :: ModuleId -> Maybe Text -> [Text] -> [Symbol] -> [Fixity] -> Map Name [Symbol] -> Maybe Parsed -> Module
- HsDev.Symbols.Types: NewType :: TypeInfo -> DeclarationInfo
+ HsDev.Symbols.Types: NewType :: [Text] -> [Text] -> SymbolInfo
- HsDev.Symbols.Types: Type :: TypeInfo -> DeclarationInfo
+ HsDev.Symbols.Types: Type :: [Text] -> [Text] -> SymbolInfo
- HsDev.Symbols.Types: [_functionType] :: DeclarationInfo -> Maybe Text
+ HsDev.Symbols.Types: [_functionType] :: SymbolInfo -> Maybe Text
- HsDev.Symbols.Types: [_inspectionOpts] :: Inspection -> [String]
+ HsDev.Symbols.Types: [_inspectionOpts] :: Inspection -> [Text]
- HsDev.Symbols.Types: [_inspectionResult] :: Inspected i t a -> Either HsDevError a
+ HsDev.Symbols.Types: [_inspectionResult] :: Inspected k t a -> Either HsDevError a
- HsDev.Symbols.Types: [_inspectionTags] :: Inspected i t a -> Set t
+ HsDev.Symbols.Types: [_inspectionTags] :: Inspected k t a -> Set t
- HsDev.Symbols.Types: [_inspection] :: Inspected i t a -> Inspection
+ HsDev.Symbols.Types: [_inspection] :: Inspected k t a -> Inspection
- HsDev.Symbols.Types: [_moduleExports] :: Module -> Maybe [Export]
+ HsDev.Symbols.Types: [_moduleExports] :: Module -> [Symbol]
- HsDev.Symbols.Types: [_moduleImports] :: Module -> [Import]
+ HsDev.Symbols.Types: [_moduleImports] :: Module -> [Text]
- HsDev.Symbols.Types: data Inspected i t a
+ HsDev.Symbols.Types: data Inspected k t a
- HsDev.Symbols.Types: functionType :: Traversal' DeclarationInfo (Maybe Text)
+ HsDev.Symbols.Types: functionType :: Traversal' SymbolInfo (Maybe Text)
- HsDev.Symbols.Types: inspection :: forall i_a1K3e t_a1K3f a_a1K3g. Lens' (Inspected i_a1K3e t_a1K3f a_a1K3g) Inspection
+ HsDev.Symbols.Types: inspection :: forall k_a22lU t_a22lV a_a22lW. Lens' (Inspected k_a22lU t_a22lV a_a22lW) Inspection
- HsDev.Symbols.Types: inspectionOpts :: Traversal' Inspection [String]
+ HsDev.Symbols.Types: inspectionOpts :: Traversal' Inspection [Text]
- HsDev.Symbols.Types: inspectionResult :: forall i_a1K3e t_a1K3f a_a1K3g a_a1Mdg. Lens (Inspected i_a1K3e t_a1K3f a_a1K3g) (Inspected i_a1K3e t_a1K3f a_a1Mdg) (Either HsDevError a_a1K3g) (Either HsDevError a_a1Mdg)
+ HsDev.Symbols.Types: inspectionResult :: forall k_a22lU t_a22lV a_a22lW a_a24tY. Lens (Inspected k_a22lU t_a22lV a_a22lW) (Inspected k_a22lU t_a22lV a_a24tY) (Either HsDevError a_a22lW) (Either HsDevError a_a24tY)
- HsDev.Symbols.Types: inspectionTags :: forall i_a1K3e t_a1K3f a_a1K3g t_a1Mdh. Lens (Inspected i_a1K3e t_a1K3f a_a1K3g) (Inspected i_a1K3e t_a1Mdh a_a1K3g) (Set t_a1K3f) (Set t_a1Mdh)
+ HsDev.Symbols.Types: inspectionTags :: forall k_a22lU t_a22lV a_a22lW t_a24tZ. Lens (Inspected k_a22lU t_a22lV a_a22lW) (Inspected k_a22lU t_a24tZ a_a22lW) (Set t_a22lV) (Set t_a24tZ)
- HsDev.Symbols.Types: moduleExports :: Lens' Module (Maybe [Export])
+ HsDev.Symbols.Types: moduleExports :: Lens' Module [Symbol]
- HsDev.Symbols.Types: moduleId :: Simple Lens Module ModuleId
+ HsDev.Symbols.Types: moduleId :: Lens' Module ModuleId
- HsDev.Symbols.Types: moduleImports :: Lens' Module [Import]
+ HsDev.Symbols.Types: moduleImports :: Lens' Module [Text]
- HsDev.Tools.AutoFix: corrections :: [Note OutputMessage] -> [Note Correction]
+ HsDev.Tools.AutoFix: corrections :: [Note OutputMessage] -> [Note Refact]
- HsDev.Tools.AutoFix: findCorrector :: Note OutputMessage -> Maybe (Note Correction)
+ HsDev.Tools.AutoFix: findCorrector :: Note OutputMessage -> Maybe (Note Refact)
- HsDev.Tools.AutoFix: match :: String -> ((Int -> Maybe String) -> Region -> Correction) -> CorrectorMatch
+ HsDev.Tools.AutoFix: match :: String -> ((Int -> Maybe Text) -> Region -> Refact) -> CorrectorMatch
- HsDev.Tools.AutoFix: type CorrectorMatch = Note OutputMessage -> Maybe (Note Correction)
+ HsDev.Tools.AutoFix: type CorrectorMatch = Note OutputMessage -> Maybe (Note Refact)
- HsDev.Tools.Base: at :: (Int -> Maybe String) -> Int -> String
+ HsDev.Tools.Base: at :: (Int -> Maybe a) -> Int -> a
- HsDev.Tools.Base: at_ :: (Int -> Maybe String) -> Int -> String
+ HsDev.Tools.Base: at_ :: IsString s => (Int -> Maybe s) -> Int -> s
- HsDev.Tools.Ghc.Check: PackageDb :: FilePath -> PackageDb
+ HsDev.Tools.Ghc.Check: PackageDb :: Path -> PackageDb
- HsDev.Tools.Ghc.Check: Project :: String -> FilePath -> FilePath -> Maybe ProjectDescription -> Project
+ HsDev.Tools.Ghc.Check: Project :: Text -> Path -> Path -> Maybe ProjectDescription -> Project
- HsDev.Tools.Ghc.Check: [_packageDb] :: PackageDb -> FilePath
+ HsDev.Tools.Ghc.Check: [_packageDb] :: PackageDb -> Path
- HsDev.Tools.Ghc.Check: [_projectCabal] :: Project -> FilePath
+ HsDev.Tools.Ghc.Check: [_projectCabal] :: Project -> Path
- HsDev.Tools.Ghc.Check: [_projectName] :: Project -> String
+ HsDev.Tools.Ghc.Check: [_projectName] :: Project -> Text
- HsDev.Tools.Ghc.Check: [_projectPath] :: Project -> FilePath
+ HsDev.Tools.Ghc.Check: [_projectPath] :: Project -> Path
- HsDev.Tools.Ghc.Check: check :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note OutputMessage]
+ HsDev.Tools.Ghc.Check: check :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m [Note OutputMessage]
- HsDev.Tools.Ghc.Compat: exposedModuleName :: ExposedModule unit mname -> mname
+ HsDev.Tools.Ghc.Compat: exposedModuleName :: (a, Maybe b) -> a
- HsDev.Tools.Ghc.Compat: type InstalledUnitId = UnitId
+ HsDev.Tools.Ghc.Compat: type InstalledUnitId = InstalledUnitId
- HsDev.Tools.Ghc.Session: interpretModule :: Module -> Maybe String -> GhcM ()
+ HsDev.Tools.Ghc.Session: interpretModule :: Module -> Maybe Text -> GhcM ()
- HsDev.Tools.Ghc.Types: TypedExpr :: String -> String -> TypedExpr
+ HsDev.Tools.Ghc.Types: TypedExpr :: Maybe Text -> Text -> TypedExpr
- HsDev.Tools.Ghc.Types: [_typedExpr] :: TypedExpr -> String
+ HsDev.Tools.Ghc.Types: [_typedExpr] :: TypedExpr -> Maybe Text
- HsDev.Tools.Ghc.Types: [_typedType] :: TypedExpr -> String
+ HsDev.Tools.Ghc.Types: [_typedType] :: TypedExpr -> Text
- HsDev.Tools.Ghc.Types: fileTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note TypedExpr]
+ HsDev.Tools.Ghc.Types: fileTypes :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m [Note TypedExpr]
- HsDev.Tools.Ghc.Types: inferTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m Module
+ HsDev.Tools.Ghc.Types: inferTypes :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m Module
- HsDev.Tools.Ghc.Types: moduleTypes :: GhcMonad m => FilePath -> m [(SrcSpan, Type)]
+ HsDev.Tools.Ghc.Types: moduleTypes :: GhcMonad m => Path -> m [(SrcSpan, Type)]
- HsDev.Tools.Ghc.Types: typedExpr :: Lens' TypedExpr String
+ HsDev.Tools.Ghc.Types: typedExpr :: Lens' TypedExpr (Maybe Text)
- HsDev.Tools.Ghc.Types: typedType :: Lens' TypedExpr String
+ HsDev.Tools.Ghc.Types: typedType :: Lens' TypedExpr Text
- HsDev.Tools.Ghc.Worker: SessionGhc :: [String] -> SessionTarget
+ HsDev.Tools.Ghc.Worker: SessionGhc :: SessionType
- HsDev.Tools.Ghc.Worker: SessionGhci :: SessionTarget
+ HsDev.Tools.Ghc.Worker: SessionGhci :: SessionType
- HsDev.Tools.Ghc.Worker: makeTarget :: GhcMonad m => String -> Maybe String -> m Target
+ HsDev.Tools.Ghc.Worker: makeTarget :: GhcMonad m => Text -> Maybe Text -> m Target
- HsDev.Tools.Ghc.Worker: type GhcM a = MGhcT SessionTarget (LogT IO) a
+ HsDev.Tools.Ghc.Worker: type GhcM a = MGhcT SessionConfig (LogT IO) a
- HsDev.Tools.Ghc.Worker: type GhcWorker = Worker (MGhcT SessionTarget (LogT IO))
+ HsDev.Tools.Ghc.Worker: type GhcWorker = Worker (MGhcT SessionConfig (LogT IO))
- HsDev.Tools.Ghc.Worker: workerSession :: SessionTarget -> GhcM ()
+ HsDev.Tools.Ghc.Worker: workerSession :: SessionType -> [String] -> GhcM ()
- HsDev.Tools.HDocs: hdocs :: ModuleLocation -> [String] -> IO (Map String String)
+ HsDev.Tools.HDocs: hdocs :: ModuleLocation -> [String] -> GhcM (Map String String)
- HsDev.Tools.HDocs: hdocsCabal :: PackageDbStack -> [String] -> ExceptT String IO (Map String (Map String String))
+ HsDev.Tools.HDocs: hdocsCabal :: PackageDbStack -> [String] -> GhcM [(ModulePackage, (Map Text (Map Text Text)))]
- HsDev.Tools.HDocs: hdocsy :: [ModuleLocation] -> [String] -> IO [Map String String]
+ HsDev.Tools.HDocs: hdocsy :: [ModuleLocation] -> [String] -> GhcM [Map String String]
- HsDev.Tools.HDocs: setDocs :: Map String String -> Module -> Module
+ HsDev.Tools.HDocs: setDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text Text -> Module -> m Module
- HsDev.Tools.HLint: hlint :: FilePath -> Maybe String -> ExceptT String IO [Note OutputMessage]
+ HsDev.Tools.HLint: hlint :: FilePath -> Maybe Text -> ExceptT String IO [Note OutputMessage]
- HsDev.Tools.Types: OutputMessage :: String -> Maybe String -> OutputMessage
+ HsDev.Tools.Types: OutputMessage :: Text -> Maybe Text -> OutputMessage
- HsDev.Tools.Types: [_messageSuggestion] :: OutputMessage -> Maybe String
+ HsDev.Tools.Types: [_messageSuggestion] :: OutputMessage -> Maybe Text
- HsDev.Tools.Types: [_message] :: OutputMessage -> String
+ HsDev.Tools.Types: [_message] :: OutputMessage -> Text
- HsDev.Tools.Types: message :: Lens' OutputMessage String
+ HsDev.Tools.Types: message :: Lens' OutputMessage Text
- HsDev.Tools.Types: messageSuggestion :: Lens' OutputMessage (Maybe String)
+ HsDev.Tools.Types: messageSuggestion :: Lens' OutputMessage (Maybe Text)
- HsDev.Tools.Types: note :: forall a_a1vCs a_a1vPy. Lens (Note a_a1vCs) (Note a_a1vPy) a_a1vCs a_a1vPy
+ HsDev.Tools.Types: note :: forall a_a1Osq a_a1OEl. Lens (Note a_a1Osq) (Note a_a1OEl) a_a1Osq a_a1OEl
- HsDev.Tools.Types: noteLevel :: forall a_a1vCs. Lens' (Note a_a1vCs) (Maybe Severity)
+ HsDev.Tools.Types: noteLevel :: forall a_a1Osq. Lens' (Note a_a1Osq) (Maybe Severity)
- HsDev.Tools.Types: noteRegion :: forall a_a1vCs. Lens' (Note a_a1vCs) Region
+ HsDev.Tools.Types: noteRegion :: forall a_a1Osq. Lens' (Note a_a1Osq) Region
- HsDev.Tools.Types: noteSource :: forall a_a1vCs. Lens' (Note a_a1vCs) ModuleLocation
+ HsDev.Tools.Types: noteSource :: forall a_a1Osq. Lens' (Note a_a1Osq) ModuleLocation
- HsDev.Tools.Types: outputMessage :: String -> OutputMessage
+ HsDev.Tools.Types: outputMessage :: Text -> OutputMessage
- HsDev.Types: FileNotFound :: FilePath -> HsDevError
+ HsDev.Types: FileNotFound :: Path -> HsDevError
- HsDev.Types: PackageNotFound :: String -> HsDevError
+ HsDev.Types: PackageNotFound :: Text -> HsDevError
- HsDev.Types: ProjectNotFound :: String -> HsDevError
+ HsDev.Types: ProjectNotFound :: Text -> HsDevError
- HsDev.Util: readFileUtf8 :: FilePath -> IO String
+ HsDev.Util: readFileUtf8 :: FilePath -> IO Text
- HsDev.Util: writeFileUtf8 :: FilePath -> String -> IO ()
+ HsDev.Util: writeFileUtf8 :: FilePath -> Text -> IO ()
- System.Directory.Paths: absolutise :: Paths a => FilePath -> a -> a
+ System.Directory.Paths: absolutise :: Paths a => Path -> a -> a
- System.Directory.Paths: relativise :: Paths a => FilePath -> a -> a
+ System.Directory.Paths: relativise :: Paths a => Path -> a -> a
Files
- data/hsdev.sql +283/−0
- hsdev.cabal +245/−318
- src/Control/Apply/Util.hs +1/−7
- src/Control/Concurrent/FiniteChan.hs +33/−49
- src/Control/Concurrent/Util.hs +8/−1
- src/Control/Concurrent/Worker.hs +34/−38
- src/Data/Async.hs +0/−47
- src/Data/Deps.hs +101/−70
- src/Data/Group.hs +0/−40
- src/Data/Help.hs +24/−21
- src/Data/LookupTable.hs +46/−0
- src/Data/Maybe/JustIf.hs +1/−9
- src/HsDev.hs +31/−33
- src/HsDev/Cache.hs +0/−79
- src/HsDev/Cache/Structured.hs +0/−72
- src/HsDev/Client/Commands.hs +622/−414
- src/HsDev/Commands.hs +0/−193
- src/HsDev/Database.hs +0/−253
- src/HsDev/Database/Async.hs +0/−30
- src/HsDev/Database/SQLite.hs +527/−0
- src/HsDev/Database/SQLite/Instances.hs +226/−0
- src/HsDev/Database/SQLite/Schema.hs +20/−0
- src/HsDev/Database/SQLite/Schema/TH.hs +16/−0
- src/HsDev/Database/SQLite/Select.hs +111/−0
- src/HsDev/Database/SQLite/Transaction.hs +98/−0
- src/HsDev/Database/Update.hs +601/−447
- src/HsDev/Database/Update/Types.hs +43/−10
- src/HsDev/Display.hs +77/−54
- src/HsDev/Error.hs +53/−63
- src/HsDev/Inspect.hs +450/−467
- src/HsDev/Inspect/Order.hs +39/−0
- src/HsDev/PackageDb.hs +63/−106
- src/HsDev/PackageDb/Types.hs +113/−0
- src/HsDev/Project.hs +197/−167
- src/HsDev/Project/Compat.hs +63/−62
- src/HsDev/Project/Types.hs +352/−298
- src/HsDev/Sandbox.hs +185/−161
- src/HsDev/Scan.hs +251/−209
- src/HsDev/Scan/Browse.hs +245/−248
- src/HsDev/Server/Base.hs +395/−159
- src/HsDev/Server/Commands.hs +180/−434
- src/HsDev/Server/Message.hs +118/−115
- src/HsDev/Server/Message/Lisp.hs +47/−0
- src/HsDev/Server/Types.hs +813/−792
- src/HsDev/Stack.hs +133/−131
- src/HsDev/Symbols.hs +127/−278
- src/HsDev/Symbols/Class.hs +30/−20
- src/HsDev/Symbols/Documented.hs +62/−24
- src/HsDev/Symbols/HaskellNames.hs +61/−0
- src/HsDev/Symbols/Location.hs +353/−277
- src/HsDev/Symbols/Name.hs +82/−0
- src/HsDev/Symbols/Parsed.hs +197/−0
- src/HsDev/Symbols/Resolve.hs +27/−176
- src/HsDev/Symbols/Types.hs +529/−620
- src/HsDev/Symbols/Util.hs +0/−203
- src/HsDev/Tools/AutoFix.hs +67/−92
- src/HsDev/Tools/Base.hs +101/−117
- src/HsDev/Tools/Cabal.hs +85/−85
- src/HsDev/Tools/ClearImports.hs +113/−112
- src/HsDev/Tools/Ghc/Check.hs +65/−88
- src/HsDev/Tools/Ghc/Compat.hs +192/−154
- src/HsDev/Tools/Ghc/MGhc.hs +215/−204
- src/HsDev/Tools/Ghc/Prelude.hs +1/−5
- src/HsDev/Tools/Ghc/Session.hs +42/−49
- src/HsDev/Tools/Ghc/Types.hs +141/−143
- src/HsDev/Tools/Ghc/Worker.hs +267/−249
- src/HsDev/Tools/HDocs.hs +179/−60
- src/HsDev/Tools/HLint.hs +99/−97
- src/HsDev/Tools/Hayoo.hs +139/−135
- src/HsDev/Tools/Refact.hs +53/−0
- src/HsDev/Tools/Types.hs +100/−99
- src/HsDev/Types.hs +124/−112
- src/HsDev/Util.hs +309/−340
- src/HsDev/Watcher.hs +73/−73
- src/System/Directory/Paths.hs +85/−31
- src/System/Directory/Watcher.hs +174/−151
- src/System/Win32/FileMapping/NamePool.hs +4/−2
- src/System/Win32/PowerShell.hs +36/−214
- tests/Test.hs +100/−47
- tests/data/ModuleTwo.modified.hs +25/−0
- tests/data/base.sql +4/−0
- tests/test-package/ModuleOne.hs +14/−14
- tools/Tool.hs +0/−48
- tools/hsautofix.hs +0/−87
- tools/hscabal.hs +0/−10
- tools/hsclearimports.hs +0/−44
- tools/hshayoo.hs +0/−22
- tools/hsinspect.hs +0/−52
+ data/hsdev.sql view
@@ -0,0 +1,283 @@+pragma case_sensitive_like = true; + +create table hsdev ( + option text, + value json +); + +create table package_dbs ( + package_db text, -- global, user or path + package_name text, + package_version text +); + +create view latest_packages ( + package_db, + package_name, + package_version +) as +select package_db, package_name, max(package_version) +from package_dbs +group by package_db, package_name; + +create table projects ( + id integer primary key autoincrement, + name text, + cabal text, + version text, + package_db_stack json -- list of package-db +); + +create unique index projects_id_index on projects (id); + +create table libraries ( + project_id integer, + modules json, -- list of modules + build_info_id integer +); + +create table executables ( + project_id integer, + name text, + path text, + build_info_id integer +); + +create table tests ( + project_id integer, + name text, + enabled integer, + main text, + build_info_id integer +); + +create view targets ( + project_id, + build_info_id +) as +select project_id, build_info_id from libraries +union +select project_id, build_info_id from executables +union +select project_id, build_info_id from tests; + +create table build_infos( + id integer primary key autoincrement, + depends json, -- list of dependencies + language text, + extensions json, -- list of extensions + ghc_options json, -- list of ghc-options + source_dirs json, -- list of source directories + other_modules json -- list of other modules +); + +create view projects_deps ( + cabal, + package_name, + package_version +) as +select distinct p.cabal, deps.value, ps.package_version +from projects as p, json_each(p.package_db_stack) as pdb_stack, build_infos as b, json_each(b.depends) as deps, targets as t, latest_packages as ps +where (p.id == t.project_id) and (b.id == t.build_info_id) and (deps.value <> p.name) and (ps.package_name == deps.value) and (ps.package_db == pdb_stack.value); + +create view projects_modules_scope ( + cabal, + module_id +) as +select pdbs.cabal, m.id +from projects_deps as pdbs, modules as m +where (m.package_name == pdbs.package_name) and (m.package_version == pdbs.package_version) +union +select p.cabal, m.id +from projects as p, modules as m +where (m.cabal == p.cabal) +union +select null, m.id +from modules as m, package_dbs as ps +where (m.package_name == ps.package_name) and (m.package_version == ps.package_version) and (ps.package_db in ('user-db', 'global-db')); + +create unique index build_infos_id_index on build_infos (id); + +create table symbols ( + id integer primary key autoincrement, + name text, + module_id integer, + docs text, + line integer, + column integer, + what text, -- kind of symbol: function, method, ... + type text, + parent text, + constructors json, -- list of constructors for selector + args json, -- list of arguments for types + context json, -- list of contexts for types + associate text, -- associates for families + pat_type text, + pat_constructor text +); + +create unique index symbols_id_index on symbols (id); +create index symbols_module_id_index on symbols (module_id); +create index symbols_name_index on symbols (name); + +create table modules ( + id integer primary key autoincrement, + file text, + cabal text, + -- project_id integer, + install_dirs json, -- list of paths + package_name text, + package_version text, + installed_name text, -- if not null, should be equal to name + other_location text, + + name text, + docs text, + fixities json, -- list of fixities + tags json, -- dict of tags, value not used, tag is set if present + inspection_error text, + inspection_time integer, + inspection_opts json -- list of flags +); + +create unique index modules_id_index on modules (id); +create index modules_name_index on modules (name); +create unique index modules_file_index on modules (file) where file is not null; +create unique index modules_installed_index on modules (package_name, package_version, installed_name) where + package_name is not null and + package_version is not null and + installed_name is not null; +create unique index modules_other_locations_index on modules (other_location) where other_location is not null; + +create table imports ( + module_id integer, + line integer, + module_name text, + qualified integer, + alias text, + hiding integer, + import_list json -- list of import specs +); + +create index imports_module_id_index on imports (module_id); + +create view imported_modules as +select i.*, im.id as imported_id +from imports as i, projects_modules_scope as ps, modules as im, modules as m +where + i.module_id == m.id and + ((ps.cabal is null and m.cabal is null) or (ps.cabal == m.cabal)) and + ps.module_id == im.id and + im.name == i.module_name; + +create view imported_scopes as +select i.*, q.value as qualifier, s.name as name, s.id as symbol_id +from imported_modules as i, json_each(case when i.qualified then json_array(ifnull(i.alias, i.module_name)) else json_array(ifnull(i.alias, i.module_name), null) end) as q, symbols as s, exports as e +where + e.module_id == i.imported_id and + e.symbol_id == s.id and + (i.import_list is null or (s.name in (select json_extract(import_list_item.value, '$.name') from json_each(i.import_list) as import_list_item) == not i.hiding)); + +create table exports ( + module_id integer, + symbol_id integer +); + +create index exports_module_id_index on exports (module_id); + +create table scopes ( + module_id integer, + qualifier text, + name text, + symbol_id integer +); + +create index scopes_module_id_index on scopes (module_id); + +create view completions ( + module_id, + completion, + symbol_id +) as +select id, (case when sc.qualifier is null then sc.name else sc.qualifier || '.' || sc.name end) as full_name, sc.symbol_id +from modules as m, scopes as sc +where (m.id == sc.module_id); + +create table names ( + module_id integer, + qualifier text, + name text, + line integer, + column integer, + line_to integer, + column_to integer, + def_line integer, + def_column integer, + inferred_type text, + resolved_module text, + resolved_name text, + resolve_error text +); + +create unique index names_position_index on names (module_id, line, column, line_to, column_to); +create index names_module_id_index on names (module_id); + +create view definitions ( + module_id, + name, + line, + column, + line_to, + column_to, + def_module_id, + def_line, + def_column, + local +) as +select module_id, name, line, column, line_to, column_to, module_id, def_line, def_column, 1 +from names +where def_line is not null and def_column is not null +union +select n.module_id, n.resolved_name, n.line, n.column, n.line_to, n.column_to, s.module_id, s.line, s.column, 0 +from names as n, modules as srcm, modules as m, projects_modules_scope as ps, symbols as s +where + (n.module_id == srcm.id) and + (srcm.cabal == ps.cabal) and + (m.id == ps.module_id) and + (m.name == n.resolved_module) and + (s.module_id == m.id) and + (s.name == n.resolved_name); + +create view sources_depends ( + module_id, + module_file, + depends_id, + depends_file +) as +select m.id, m.file, im.id, im.file +from modules as im, imports as i, modules as m, projects_modules_scope as ps +where + (m.cabal is ps.cabal) and + (ps.module_id == im.id) and + (i.module_id == m.id) and + (im.name == i.module_name) and + (m.file is not null) and + (im.file is not null); + +create table types ( + module_id integer, + line integer, + column integer, + line_to integer, + column_to integer, + expr text, + type text +); + +create table file_contents ( + file text not null, + contents text, + mtime integer +); + +create unique index file_contents_index on file_contents (file);
hsdev.cabal view
@@ -1,318 +1,245 @@-name: hsdev-version: 0.2.5.1-synopsis: Haskell development library-description:- Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc.-homepage: https://github.com/mvoidex/hsdev-license: BSD3-license-file: LICENSE-author: Alexandr `Voidex` Ruchkin-maintainer: voidex@live.com--- copyright:-category: Development-build-type: Simple-cabal-version: >=1.8-extra-source-files:- tests/test-package/*.hs- tests/test-package/test-package.cabal--source-repository head- type: git- location: git://github.com/mvoidex/hsdev.git--library- hs-source-dirs: src- ghc-options: -Wall -fno-warn-tabs- exposed-modules:- Control.Apply.Util- Control.Concurrent.FiniteChan- Control.Concurrent.Worker- Control.Concurrent.Util- Data.Async- Data.Deps- Data.Group- Data.Help- Data.Lisp- Data.Maybe.JustIf- HsDev- HsDev.Cache- HsDev.Cache.Structured- HsDev.Client.Commands- HsDev.Commands- HsDev.Database- HsDev.Database.Async- HsDev.Database.Update- HsDev.Database.Update.Types- HsDev.Display- HsDev.Error- HsDev.Inspect- HsDev.PackageDb- HsDev.Project- HsDev.Project.Compat- HsDev.Project.Types- HsDev.Scan- HsDev.Scan.Browse- HsDev.Server.Base- HsDev.Server.Commands- HsDev.Server.Message- HsDev.Server.Types- HsDev.Sandbox- HsDev.Stack- HsDev.Symbols- HsDev.Symbols.Class- HsDev.Symbols.Location- HsDev.Symbols.Documented- HsDev.Symbols.Resolve- HsDev.Symbols.Types- HsDev.Symbols.Util- HsDev.Tools.AutoFix- HsDev.Tools.Base- HsDev.Tools.Cabal- HsDev.Tools.ClearImports- HsDev.Tools.Ghc.Check- HsDev.Tools.Ghc.Compat- HsDev.Tools.Ghc.MGhc- HsDev.Tools.Ghc.Prelude- HsDev.Tools.Ghc.Types- HsDev.Tools.Ghc.Worker- HsDev.Tools.Ghc.Session- HsDev.Tools.Hayoo- HsDev.Tools.HDocs- HsDev.Tools.HLint- HsDev.Tools.Types- HsDev.Types- HsDev.Util- HsDev.Version- HsDev.Watcher- HsDev.Watcher.Types- System.Directory.Paths- System.Directory.Watcher-- if os(windows)- build-depends:- Win32 >= 2.3.0- exposed-modules:- System.Win32.PowerShell- System.Win32.FileMapping.NamePool- System.Win32.FileMapping.Memory- else- build-depends:- unix >= 2.7.0-- if impl(ghc >= 8.2) && impl(ghc < 8.3)- build-depends:- haddock-api >= 2.18.0 && < 2.19.0,- ghc == 8.2.*,- ghc-boot,- directory >= 1.2.6.0- if impl(ghc >= 8.0) && impl(ghc < 8.2)- build-depends:- haddock-api >= 2.17.0 && < 2.18.0,- ghc >= 8.0.0 && < 8.1.0,- ghc-boot,- directory >= 1.2.6.0- if impl(ghc < 8.0)- build-depends:- haddock-api >= 2.16.0 && < 2.17.0,- ghc >= 7.10.0 && < 7.11.0,- bin-package-db,- directory == 1.2.2.*-- build-depends:- base >= 4.7 && < 5,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- array >= 0.5.0,- async >= 2.0,- attoparsec >= 0.11.0,- bytestring >= 0.10.0,- Cabal >= 1.22.0,- containers >= 0.5.0,- cpphs >= 1.19.0,- data-default >= 0.5.0,- deepseq >= 1.4.0,- exceptions >= 0.6.0,- filepath >= 1.4.0,- fsnotify >= 0.2.1,- ghc-paths >= 0.1.0,- ghc-syb-utils >= 0.2.3,- haskell-src-exts >= 1.18.0 && < 1.20.0,- hdocs >= 0.5.0,- hformat == 0.3.*,- hlint >= 1.9.13 && < 2.1,- HTTP >= 4000.2.0,- lens >= 4.8,- lifted-base >= 0.2,- monad-control >= 1.0,- monad-loops >= 0.4,- mmorph >= 1.0 && < 1.2,- mtl >= 2.2.0,- network >= 2.6.0,- optparse-applicative >= 0.11,- process >= 1.2.0,- regex-pcre-builtin >= 0.94,- scientific >= 0.3,- simple-log == 0.9.*,- syb >= 0.5.1,- template-haskell,- text >= 1.2.0,- text-region == 0.3.*,- time >= 1.5.0,- transformers >= 0.4.0,- transformers-base >= 0.4.0,- uniplate >= 1.6.0,- unordered-containers >= 0.2.0,- vector >= 0.10.0--executable hsdev- main-is: hsdev.hs- hs-source-dirs: tools- ghc-options: -threaded -Wall -fno-warn-tabs "-with-rtsopts=-N4"-- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- bytestring >= 0.10.0,- containers >= 0.5.0,- deepseq >= 1.4.0,- directory,- exceptions >= 0.6.0,- filepath >= 1.4.0,- monad-loops >= 0.4.0,- mtl >= 2.2.0,- network >= 2.6.0,- optparse-applicative >= 0.11,- process >= 1.2.0,- text >= 1.2.0,- transformers >= 0.4.0,- unordered-containers >= 0.2.0--executable hsinspect- main-is: hsinspect.hs- hs-source-dirs: tools- ghc-options: -Wall -fno-warn-tabs- other-modules:- Tool- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- bytestring >= 0.10.0,- containers >= 0.5.0,- data-default >= 0.5.0,- directory,- filepath >= 1.4.0,- mtl >= 2.2.0,- optparse-applicative >= 0.11,- text >= 1.2.0,- transformers >= 0.4.0,- unordered-containers >= 0.2.0,- vector >= 0.10.0--executable hsclearimports- main-is: hsclearimports.hs- hs-source-dirs: tools- ghc-options: -Wall -fno-warn-tabs- other-modules:- Tool- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- bytestring >= 0.10.0,- containers >= 0.5.0,- data-default >= 0.5.0,- directory,- haskell-src-exts >= 1.18.0 && < 1.20.0,- lens >= 4.8,- mtl >= 2.2.0,- optparse-applicative >= 0.11,- text >= 1.2.0,- transformers >= 0.4.0,- unordered-containers >= 0.2.0--executable hscabal- main-is: hscabal.hs- hs-source-dirs: tools- ghc-options: -Wall -fno-warn-tabs- other-modules:- Tool- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- bytestring >= 0.10.0,- containers >= 0.5.0,- data-default >= 0.5.0,- mtl >= 2.2.0,- optparse-applicative >= 0.11,- text >= 1.2.0,- transformers >= 0.4.0,- unordered-containers >= 0.2.0--executable hshayoo- main-is: hshayoo.hs- hs-source-dirs: tools- ghc-options: -Wall -fno-warn-tabs- other-modules:- Tool- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- bytestring >= 0.10.0,- containers >= 0.5.0,- data-default >= 0.5.0,- mtl >= 2.2.0,- optparse-applicative >= 0.11,- text >= 1.2.0,- transformers >= 0.4.0,- unordered-containers >= 0.2.0--executable hsautofix- main-is: hsautofix.hs- hs-source-dirs: tools- ghc-options: -Wall -fno-warn-tabs- other-modules:- Tool- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-pretty >= 0.7.0,- bytestring >= 0.10.0,- data-default >= 0.5.0,- directory,- filepath >= 1.4.0,- lens >= 4.8,- mtl >= 2.2.0,- optparse-applicative >= 0.11,- transformers >= 0.4.0--test-suite test- main-is: Test.hs- hs-source-dirs: tests- ghc-options: -threaded -Wall -fno-warn-tabs- type: exitcode-stdio-1.0- build-depends:- base >= 4.7 && < 5,- hsdev,- aeson >= 0.7.0,- aeson-lens >= 0.5.0,- async >= 2.0,- data-default >= 0.5.0,- deepseq >= 1.4.0,- directory,- filepath >= 1.4.0,- containers >= 0.5.0,- hformat == 0.3.*,- hspec,- lens >= 4.8,- mtl >= 2.2.0,- text >= 1.2.0+name: hsdev +version: 0.3.0.0 +synopsis: Haskell development library +description: + Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc. +homepage: https://github.com/mvoidex/hsdev +license: BSD3 +license-file: LICENSE +author: Alexandr `Voidex` Ruchkin +maintainer: voidex@live.com +-- copyright: +category: Development +build-type: Simple +cabal-version: >=1.8 +extra-source-files: + tests/test-package/*.hs + tests/test-package/test-package.cabal + tests/data/base.sql + tests/data/ModuleTwo.modified.hs + data/hsdev.sql + +source-repository head + type: git + location: git://github.com/mvoidex/hsdev.git + +flag docs + description: build with haddock/hdocs to support scanning docs + default: True + +library + hs-source-dirs: src + ghc-options: -Wall -fno-warn-tabs + + if !flag(docs) + cpp-options: -DNODOCS + + exposed-modules: + Control.Apply.Util + Control.Concurrent.FiniteChan + Control.Concurrent.Worker + Control.Concurrent.Util + Data.Deps + Data.Help + Data.Lisp + Data.Maybe.JustIf + Data.LookupTable + HsDev + HsDev.Client.Commands + HsDev.Database.SQLite + HsDev.Database.SQLite.Instances + HsDev.Database.SQLite.Schema + HsDev.Database.SQLite.Schema.TH + HsDev.Database.SQLite.Select + HsDev.Database.SQLite.Transaction + HsDev.Database.Update + HsDev.Database.Update.Types + HsDev.Display + HsDev.Error + HsDev.Inspect + HsDev.Inspect.Order + HsDev.PackageDb + HsDev.PackageDb.Types + HsDev.Project + HsDev.Project.Compat + HsDev.Project.Types + HsDev.Scan + HsDev.Scan.Browse + HsDev.Server.Base + HsDev.Server.Commands + HsDev.Server.Message + HsDev.Server.Message.Lisp + HsDev.Server.Types + HsDev.Sandbox + HsDev.Stack + HsDev.Symbols + HsDev.Symbols.Name + HsDev.Symbols.Class + HsDev.Symbols.HaskellNames + HsDev.Symbols.Location + HsDev.Symbols.Documented + HsDev.Symbols.Resolve + HsDev.Symbols.Parsed + HsDev.Symbols.Types + HsDev.Tools.AutoFix + HsDev.Tools.Base + HsDev.Tools.Cabal + HsDev.Tools.ClearImports + HsDev.Tools.Ghc.Check + HsDev.Tools.Ghc.Compat + HsDev.Tools.Ghc.MGhc + HsDev.Tools.Ghc.Prelude + HsDev.Tools.Ghc.Session + HsDev.Tools.Ghc.Types + HsDev.Tools.Ghc.Worker + HsDev.Tools.Hayoo + HsDev.Tools.HDocs + HsDev.Tools.HLint + HsDev.Tools.Refact + HsDev.Tools.Types + HsDev.Types + HsDev.Util + HsDev.Version + HsDev.Watcher + HsDev.Watcher.Types + System.Directory.Paths + System.Directory.Watcher + + if os(windows) + build-depends: + Win32 >= 2.3.0 + exposed-modules: + System.Win32.PowerShell + System.Win32.FileMapping.NamePool + System.Win32.FileMapping.Memory + else + build-depends: + unix >= 2.7.0 + + if impl(ghc >= 8.2) && impl(ghc < 8.3) + if flag(docs) + build-depends: + haddock-api >= 2.18.0 && < 2.19.0 + + build-depends: + ghc == 8.2.*, + ghc-boot, + directory >= 1.2.6.0 + if impl(ghc >= 8.0) && impl(ghc < 8.2) + if flag(docs) + build-depends: + haddock-api >= 2.17.0 && < 2.18.0 + + build-depends: + ghc >= 8.0.0 && < 8.1.0, + ghc-boot, + directory >= 1.2.6.0 + if impl(ghc < 8.0) + if flag(docs) + build-depends: + haddock-api >= 2.16.0 && < 2.17.0 + + build-depends: + ghc >= 7.10.0 && < 7.11.0, + bin-package-db, + directory == 1.2.2.* + + if flag(docs) + build-depends: + hdocs >= 0.5.1 + + build-depends: + base >= 4.7 && < 5, + aeson >= 0.7.0, + aeson-pretty >= 0.7.0, + array >= 0.5.0, + async >= 2.0, + attoparsec >= 0.11.0, + bytestring >= 0.10.0, + Cabal >= 1.22.0, + containers >= 0.5, + cpphs >= 1.19.0, + data-default >= 0.5.0, + deepseq >= 1.4.0, + exceptions >= 0.6.0, + filepath >= 1.4.0, + fsnotify >= 0.2.1, + ghc-paths >= 0.1.0, + ghc-syb-utils >= 0.2.3, + haskell-names >= 0.9.0 && < 0.10.0, + haskell-src-exts >= 1.18.0 && < 1.21.0, + hformat >= 0.3.0, + hlint >= 1.9.13 && < 2.1, + HTTP >= 4000.2.0, + lens >= 4.8, + lifted-base >= 0.2, + monad-control >= 1.0, + monad-loops >= 0.4, + mmorph >= 1.0 && < 1.2, + mtl >= 2.2.0, + network >= 2.6.0, + optparse-applicative >= 0.11, + process >= 1.2.0, + regex-pcre-builtin >= 0.94, + scientific >= 0.3, + simple-log == 0.9.*, + stm == 2.4.*, + direct-sqlite >= 2.3.21, + sqlite-simple, + syb >= 0.5.1, + template-haskell, + text >= 1.2.0, + text-region >= 0.3, + time >= 1.5.0, + transformers >= 0.4.0, + transformers-base >= 0.4.0, + uniplate >= 1.6.0, + unordered-containers >= 0.2.0, + vector >= 0.10.0 + +executable hsdev + main-is: hsdev.hs + hs-source-dirs: tools + ghc-options: -threaded -Wall -fno-warn-tabs "-with-rtsopts=-N4" + + build-depends: + base >= 4.7 && < 5, + hsdev, + aeson >= 0.7.0, + aeson-pretty >= 0.7.0, + bytestring >= 0.10.0, + containers >= 0.5, + deepseq >= 1.4.0, + directory, + exceptions >= 0.6.0, + filepath >= 1.4.0, + monad-loops >= 0.4.0, + mtl >= 2.2.0, + network >= 2.6.0, + optparse-applicative >= 0.11, + process >= 1.2.0, + text >= 1.2.0, + transformers >= 0.4.0, + unordered-containers >= 0.2.0 + +test-suite test + main-is: Test.hs + hs-source-dirs: tests + ghc-options: -threaded -Wall -fno-warn-tabs + type: exitcode-stdio-1.0 + build-depends: + base >= 4.7 && < 5, + hsdev, + aeson >= 0.7.0, + aeson-lens >= 0.5.0, + async >= 2.0, + data-default >= 0.5.0, + deepseq >= 1.4.0, + directory, + filepath >= 1.4.0, + containers >= 0.5.0, + hformat == 0.3.*, + hspec, + lens >= 4.8, + mtl >= 2.2.0, + text >= 1.2.0
src/Control/Apply/Util.hs view
@@ -1,5 +1,5 @@ module Control.Apply.Util ( - (&), chain, with + (&), chain ) where (&) :: a -> (a -> b) -> b @@ -7,9 +7,3 @@ chain :: [a -> a] -> a -> a chain = foldr (.) id - --- | Flipped version of 'chain', which can be used like this: --- --- >foo `with` [f, g, h] -with :: a -> [a -> a] -> a -with = flip chain
src/Control/Concurrent/FiniteChan.hs view
@@ -1,73 +1,57 @@ module Control.Concurrent.FiniteChan ( Chan, - newChan, dupChan, openedChan, closedChan, doneChan, sendChan, putChan, getChan, readChan, closeChan, stopChan + newChan, openedChan, closedChan, doneChan, sendChan, getChan, closeChan, stopChan ) where import Control.Monad (void, when, liftM2) -import qualified Control.Concurrent.Chan as C -import Control.Concurrent.MVar -import Data.Maybe - -data State = Opened | Closing | Closed deriving (Eq, Ord, Enum, Bounded, Read, Show) +import Control.Monad.Loops +import Control.Concurrent.STM --- | 'Chan' is stoppable channel unline 'Control.Concurrent.Chan' -data Chan a = Chan (C.Chan (Maybe a)) (MVar State) +-- | 'Chan' is stoppable channel +data Chan a = Chan { + chanOpened :: TMVar Bool, + chanQueue :: TQueue a } --- | Create channel +-- -- | Create new channel newChan :: IO (Chan a) -newChan = liftM2 Chan C.newChan (newMVar Opened) - --- | Duplicate channel -dupChan :: Chan a -> IO (Chan a) -dupChan (Chan ch st) = liftM2 Chan (C.dupChan ch) (return st) +newChan = liftM2 Chan (newTMVarIO True) newTQueueIO -- | Is channel opened openedChan :: Chan a -> IO Bool -openedChan (Chan _ st) = (== Opened) <$> readMVar st +openedChan = atomically . readTMVar . chanOpened --- | Is channel closing/closed +-- | Is channel closed closedChan :: Chan a -> IO Bool -closedChan (Chan _ st) = (/= Opened) <$> readMVar st +closedChan = fmap not . openedChan --- | Is channed closed and all data allready read from it +-- | Is channel closed and all data consumed doneChan :: Chan a -> IO Bool -doneChan (Chan _ st) = (== Closed) <$> readMVar st +doneChan ch = atomically $ do + o <- readTMVar $ chanOpened ch + e <- isEmptyTQueue (chanQueue ch) + return $ not o && e --- | Write data to channel +-- | Write data to channel if it is open sendChan :: Chan a -> a -> IO Bool -sendChan (Chan ch st) v = do - state <- readMVar st - if state == Opened - then do - C.writeChan ch (Just v) - return True - else return False - --- | Put data to channel -putChan :: Chan a -> a -> IO () -putChan ch = void . sendChan ch +sendChan ch v = atomically $ do + o <- readTMVar $ chanOpened ch + when o $ writeTQueue (chanQueue ch) v + return o -- | Get data from channel getChan :: Chan a -> IO (Maybe a) -getChan (Chan ch st) = do - state <- readMVar st - if state == Closed then return Nothing else do - r <- C.readChan ch - when (isNothing r) (void $ swapMVar st Closed) - return r - --- | Read channel contents -readChan :: Chan a -> IO [a] -readChan (Chan ch _) = (catMaybes . takeWhile isJust) <$> C.getChanContents ch +getChan ch = atomically $ do + o <- readTMVar $ chanOpened ch + if o + then fmap Just (readTQueue (chanQueue ch)) + else tryReadTQueue (chanQueue ch) --- | Close channel. 'putChan' will still work, but no data will be available on other ending +-- | Close channel closeChan :: Chan a -> IO () -closeChan (Chan ch st) = do - state <- readMVar st - when (state == Opened) $ do - _ <- swapMVar st Closing - C.writeChan ch Nothing +closeChan ch = atomically $ void $ swapTMVar (chanOpened ch) False --- | Stop channel and return all data +-- | Close channel and read all messages stopChan :: Chan a -> IO [a] -stopChan ch = closeChan ch >> readChan ch +stopChan ch = do + closeChan ch + whileJust (getChan ch) return
src/Control/Concurrent/Util.hs view
@@ -1,5 +1,5 @@ module Control.Concurrent.Util ( - fork, timeout + fork, timeout, sync ) where import Control.Concurrent @@ -13,3 +13,10 @@ timeout :: Int -> IO a -> IO (Maybe a) timeout 0 act = liftM Just act timeout tm act = liftM (either Just (const Nothing)) $ race act (threadDelay tm) + +sync :: MonadIO m => (IO () -> m a) -> m a +sync act = do + syncVar <- liftIO newEmptyMVar + r <- act $ putMVar syncVar () + liftIO $ takeMVar syncVar + return r
src/Control/Concurrent/Worker.hs view
@@ -2,10 +2,9 @@ module Control.Concurrent.Worker ( Worker(..), WorkerStopped(..), - startWorker, workerDone, - sendTask, pushTask, - restartWorker, stopWorker, syncTask, - inWorkerWith, inWorker, inWorker_, + startWorker, workerAlive, workerDone, + sendTask, stopWorker, joinWorker, syncTask, + inWorkerWith, inWorker, module Control.Concurrent.Async ) where @@ -14,7 +13,7 @@ import Control.Monad.IO.Class import Control.Monad.Catch import Control.Monad.Except -import Data.Maybe (isJust) +import Data.Maybe (isNothing) import Data.Typeable import Control.Concurrent.FiniteChan @@ -22,34 +21,36 @@ data Worker m = Worker { workerChan :: Chan (Async (), m ()), - workerWrap :: forall a. m a -> m a, - workerTask :: MVar (Async ()), - workerTouch :: IO () } + workerTask :: MVar (Async ()) } data WorkerStopped = WorkerStopped deriving (Show, Typeable) instance Exception WorkerStopped -- | Create new worker -startWorker :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> (forall a. m a -> m a) -> IO (Worker m) -startWorker run initialize wrap = do +startWorker :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> (m () -> m ()) -> IO (Worker m) +startWorker run initialize handleErrs = do ch <- newChan taskVar <- newEmptyMVar let - start = async $ run $ initialize go + job = onException (run $ initialize go) $ do + closeChan ch + abort go = do t <- fmap snd <$> liftIO (getChan ch) - maybe (return ()) (>> go) t - start >>= putMVar taskVar - let - restart = do - done <- doneChan ch - unless done $ do - task <- readMVar taskVar - stopped <- isJust <$> poll task - when stopped (start >>= void . swapMVar taskVar) - return $ Worker ch wrap taskVar restart + maybe (return ()) (\f' -> handleErrs f' >> go) t + abort = do + a <- fmap fst <$> liftIO (getChan ch) + maybe (return ()) (\a' -> liftIO (cancel a') >> abort) a + async job >>= putMVar taskVar + return $ Worker ch taskVar +-- | Check whether worker alive +workerAlive :: Worker m -> IO Bool +workerAlive w = do + task <- readMVar $ workerTask w + isNothing <$> poll task + workerDone :: Worker m -> IO Bool workerDone = doneChan . workerChan @@ -57,7 +58,7 @@ sendTask w act = mfix $ \async' -> do var <- newEmptyMVar let - act' = (workerWrap w act >>= liftIO . putMVar var . Right) `catch` onError + act' = (act >>= liftIO . putMVar var . Right) `catch` onError onError :: MonadIO m => SomeException -> m () onError = liftIO . putMVar var . Left f = do @@ -67,30 +68,25 @@ either throwM return r async f -pushTask :: (MonadCatch m, MonadIO m) => Worker m -> m a -> IO (Async a) -pushTask w act = workerTouch w >> sendTask w act - -restartWorker :: Worker m -> IO () -restartWorker w = do - async' <- readMVar (workerTask w) - cancel async' - void $ waitCatch async' - +-- | Close worker channel stopWorker :: Worker m -> IO () stopWorker = closeChan . workerChan +-- | Stop worker and wait for it +joinWorker :: Worker m -> IO () +joinWorker w = do + stopWorker w + async' <- readMVar $ workerTask w + void $ waitCatch async' + -- | Send empty task and wait until worker run it syncTask :: (MonadCatch m, MonadIO m) => Worker m -> IO () -syncTask w = pushTask w (return ()) >>= void . wait +syncTask w = sendTask w (return ()) >>= void . wait -- | Run action in worker and wait for result inWorkerWith :: (MonadIO m, MonadCatch m, MonadIO n) => (SomeException -> n a) -> Worker m -> m a -> n a -inWorkerWith err w act = liftIO (pushTask w act) >>= (liftIO . waitCatch >=> either err return) +inWorkerWith err w act = liftIO (sendTask w act) >>= (liftIO . waitCatch >=> either err return) -- | Run action in worker and wait for result inWorker :: (MonadIO m, MonadCatch m) => Worker m -> m a -> IO a -inWorker w act = pushTask w act >>= liftIO . wait - --- | Run action in worker and wait for result -inWorker_ :: (MonadIO m, MonadCatch m) => Worker m -> m a -> ExceptT SomeException IO a -inWorker_ w act = liftIO (pushTask w act) >>= ExceptT . waitCatch +inWorker w act = sendTask w act >>= liftIO . wait
− src/Data/Async.hs
@@ -1,47 +0,0 @@-module Data.Async ( - Event(..), - event, - Async(..), - newAsync, readAsync, modifyAsync, - - -- * Reexports - Group, NFData - ) where - -import Control.DeepSeq (NFData, force) -import Control.Monad (forM_) -import Control.Concurrent - -import Data.Group (Group(..)) - --- | Event on async value -data Event a = Append a | Remove a | Clear | Modify (a -> a) | Action (a -> IO a) - --- | Event to function -event :: Group a => Event a -> a -> IO a -event (Append v) x = return $ add x v -event (Remove v) x = return $ sub x v -event Clear _ = return zero -event (Modify p) x = return $ p x -event (Action p) x = p x - -data Async a = Async { - asyncVar :: MVar a, - asyncEvents :: Chan (Event a) } - -newAsync :: (NFData a, Group a) => IO (Async a) -newAsync = do - var <- newMVar zero - events <- newChan - _ <- forkIO $ do - evs <- getChanContents events - forM_ evs $ \e -> modifyMVar_ var $ \val -> do - x' <- event e val - force x' `seq` return x' - return $ Async var events - -readAsync :: Async a -> IO a -readAsync = readMVar . asyncVar - -modifyAsync :: Async a -> Event a -> IO () -modifyAsync avar = writeChan (asyncEvents avar)
src/Data/Deps.hs view
@@ -1,70 +1,101 @@-{-# LANGUAGE FlexibleContexts, TypeFamilies #-}--module Data.Deps (- Deps(..), depsMap,- mapDeps,- dep, deps,- inverse, flatten- ) where--import Control.Lens-import Control.Monad.State-import Data.List (nub)-import Data.Map (Map)-import qualified Data.Map as M-import Data.Maybe (fromMaybe)---- | Dependency map-data Deps a = Deps {- _depsMap :: Map a [a] }--depsMap :: Lens (Deps a) (Deps b) (Map a [a]) (Map b [b])-depsMap = lens _depsMap (const Deps)--instance Ord a => Monoid (Deps a) where- mempty = Deps mempty- mappend (Deps l) (Deps r) = Deps $ M.unionWith nubConcat l r--type instance Index (Deps a) = a-type instance IxValue (Deps a) = [a]--instance Ord a => Ixed (Deps a) where- ix k = depsMap . ix k--instance Ord a => At (Deps a) where- at k = depsMap . at k--mapDeps :: Ord b => (a -> b) -> Deps a -> Deps b-mapDeps f = Deps . M.mapKeys f . M.map (map f) . _depsMap---- | Make single dependency-dep :: a -> a -> Deps a-dep x y = deps x [y]---- | Make dependency for one target, note that order of dependencies is matter-deps :: a -> [a] -> Deps a-deps x ys = Deps $ M.singleton x ys---- | Inverse dependencies, i.e. make map where keys are dependencies and elements are targets depends on it-inverse :: Ord a => Deps a -> Deps a-inverse = mconcat . map (uncurry dep) . concatMap inverse' . M.toList . _depsMap where- inverse' :: (a, [a]) -> [(a, a)]- inverse' (m, ds) = zip ds (repeat m)---- | Flatten dependencies so that there will be no indirect dependencies-flatten :: Ord a => Deps a -> Deps a-flatten (Deps ds) = flip execState mempty . mapM_ flatten' . M.keys $ ds where- -- flatten' :: a -> State (Deps a) [a]- flatten' n = do- d <- gets (M.lookup n . _depsMap)- case d of- Just d' -> return d'- Nothing -> do- let- deps' = fromMaybe [] $ M.lookup n ds- d'' <- (nub . concat . (++ [deps'])) <$> mapM flatten' deps'- modify $ mappend (deps n d'')- return d''--nubConcat :: Ord a => [a] -> [a] -> [a]-nubConcat xs ys = nub $ xs ++ ys+{-# LANGUAGE FlexibleContexts, TypeFamilies #-} + +module Data.Deps ( + Deps(..), depsMap, + mapDeps, + dep, deps, + inverse, + DepsError(..), flatten, + selfDepend, + linearize + ) where + +import Control.Lens +import Control.Monad.State +import Control.Monad.Except +import Data.List (nub, intercalate) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe) + +-- | Dependency map +newtype Deps a = Deps { + _depsMap :: Map a [a] } + +depsMap :: Lens (Deps a) (Deps b) (Map a [a]) (Map b [b]) +depsMap = lens _depsMap (const Deps) + +instance Ord a => Monoid (Deps a) where + mempty = Deps mempty + mappend (Deps l) (Deps r) = Deps $ M.unionWith nubConcat l r + +instance Show a => Show (Deps a) where + show (Deps ds) = unlines [show d ++ " -> " ++ intercalate ", " (map show s) | (d, s) <- M.toList ds] + +type instance Index (Deps a) = a +type instance IxValue (Deps a) = [a] + +instance Ord a => Ixed (Deps a) where + ix k = depsMap . ix k + +instance Ord a => At (Deps a) where + at k = depsMap . at k + +mapDeps :: Ord b => (a -> b) -> Deps a -> Deps b +mapDeps f = Deps . M.mapKeys f . M.map (map f) . _depsMap + +-- | Make single dependency +dep :: a -> a -> Deps a +dep x y = deps x [y] + +-- | Make dependency for one target, note that order of dependencies is matter +deps :: a -> [a] -> Deps a +deps x ys = Deps $ M.singleton x ys + +-- | Inverse dependencies, i.e. make map where keys are dependencies and elements are targets depends on it +inverse :: Ord a => Deps a -> Deps a +inverse = mconcat . map (uncurry dep) . concatMap inverse' . M.toList . _depsMap where + inverse' :: (a, [a]) -> [(a, a)] + inverse' (m, ds) = zip ds (repeat m) + +newtype DepsError a = + CyclicDeps [a] + -- ^ Dependency cycle, list is cycle, where last item depends on first + deriving (Eq, Ord, Read) + +instance Show a => Show (DepsError a) where + show (CyclicDeps c) = "dependencies forms a cycle: " ++ concat [show d ++ " -> " | d <- c] ++ "..." + +-- | Flatten dependencies so that there will be no indirect dependencies +flatten :: Ord a => Deps a -> Either (DepsError a) (Deps a) +flatten s@(Deps ds) = fmap snd . flip execStateT mempty . mapM_ (flatten' s) . M.keys $ ds where + flatten' :: Ord a => Deps a -> a -> StateT ([a], Deps a) (Either (DepsError a)) [a] + flatten' s' n = do + path <- gets (view _1) + when (preview (reversed . each) path == Just n) $ throwError (CyclicDeps $ reverse path) + d <- gets (preview $ _2 . ix n) + case d of + Just d' -> return d' + Nothing -> pushPath n $ do + d'' <- (nub . concat . (++ [deps'])) <$> mapM (flatten' s') deps' + modify (over _2 $ mappend (deps n d'')) + return d'' + where + deps' = fromMaybe [] $ preview (ix n) s' + pushPath :: MonadState ([a], Deps a) m => a -> m b -> m b + pushPath p act = do + modify (over _1 (p:)) + r <- act + modify (over _1 tail) + return r + +selfDepend :: Deps a -> Deps a +selfDepend = Deps . M.mapWithKey (\s d -> d ++ [s]) . _depsMap + +-- | Linearize dependencies so that all items can be processed in this order, +-- i.e. for each item all its dependencies goes before +linearize :: Ord a => Deps a -> Either (DepsError a) [a] +linearize = fmap (nub . concat . toListOf (depsMap . each) . selfDepend) . flatten + +nubConcat :: Ord a => [a] -> [a] -> [a] +nubConcat xs ys = nub $ xs ++ ys
− src/Data/Group.hs
@@ -1,40 +0,0 @@-{-# LANGUAGE TypeSynonymInstances #-}--module Data.Group (- Group(..),- groupSum- ) where--import Data.List ((\\))-import Data.Map (Map)-import qualified Data.Map as M-import Data.Set (Set)-import qualified Data.Set as S---- | Group is monoid with invertibility--- But for our purposes we prefer two functions: `add` and `sub`.-class Eq a => Group a where- add :: a -> a -> a- sub :: a -> a -> a- zero :: a--instance Eq a => Group [a] where- add = (++)- sub = (\\)- zero = []--instance Ord a => Group (Set a) where- add = S.union- sub = S.difference- zero = S.empty--instance (Ord k, Group a) => Group (Map k a) where- add = M.unionWith add- sub = M.differenceWith sub' where- sub' x y = if z == zero then Nothing else Just z where- z = sub x y- zero = M.empty---- | Sums list-groupSum :: Group a => [a] -> a-groupSum = foldr add zero
src/Data/Help.hs view
@@ -1,21 +1,24 @@-module Data.Help (- Help(..),- indent, indentHelp, detailed, indented- ) where--class Help a where- brief :: a -> String- help :: a -> [String]--indent :: String -> String-indent = ('\t':)--indentHelp :: [String] -> [String]-indentHelp [] = []-indentHelp (h:hs) = h : map indent hs--detailed :: Help a => a -> [String]-detailed v = brief v : help v--indented :: Help a => a -> [String]-indented = indentHelp . detailed+module Data.Help ( + Help(..), + indent, indentHelp, detailed, indented + ) where + +import Data.Text (Text) +import qualified Data.Text as T + +class Help a where + brief :: a -> Text + help :: a -> [Text] + +indent :: Text -> Text +indent = T.cons '\t' + +indentHelp :: [Text] -> [Text] +indentHelp [] = [] +indentHelp (h:hs) = h : map indent hs + +detailed :: Help a => a -> [Text] +detailed v = brief v : help v + +indented :: Help a => a -> [Text] +indented = indentHelp . detailed
+ src/Data/LookupTable.hs view
@@ -0,0 +1,46 @@+module Data.LookupTable ( + LookupTable, + newLookupTable, + lookupTable, lookupTableM, cacheInTableM, + hasLookupTable, + cachedInTable + ) where + +import Control.Monad.IO.Class +import Control.Concurrent.MVar +import Data.Map (Map) +import qualified Data.Map as M + +-- | k-v table +type LookupTable k v = MVar (Map k v) + +newLookupTable :: (Ord k, MonadIO m) => m (LookupTable k v) +newLookupTable = liftIO $ newMVar mempty + +-- | Lookup, or insert if not exists +lookupTable :: (Ord k, MonadIO m) => k -> v -> LookupTable k v -> m v +lookupTable key value tbl = liftIO $ modifyMVar tbl $ \tbl' -> case M.lookup key tbl' of + Just value' -> return (tbl', value') + Nothing -> return (M.insert key value tbl', value) + +-- | Lookup, or insert if not exists +lookupTableM :: (Ord k, MonadIO m) => k -> m v -> LookupTable k v -> m v +lookupTableM key mvalue tbl = do + mv <- hasLookupTable key tbl + case mv of + Just value -> return value + Nothing -> do + value <- mvalue + lookupTable key value tbl + +-- | @lookupTableM@ with swapped args +cacheInTableM :: (Ord k, MonadIO m) => LookupTable k v -> k -> m v -> m v +cacheInTableM tbl key mvalue = lookupTableM key mvalue tbl + +-- | Just check existable +hasLookupTable :: (Ord k, MonadIO m) => k -> LookupTable k v -> m (Maybe v) +hasLookupTable key tbl = liftIO $ withMVar tbl $ return . M.lookup key + +-- | Make function caching results in @LookupTable@ +cachedInTable :: (Ord k, MonadIO m) => LookupTable k v -> (k -> m v) -> k -> m v +cachedInTable tbl fn key = cacheInTableM tbl key (fn key)
src/Data/Maybe/JustIf.hs view
@@ -1,16 +1,8 @@ module Data.Maybe.JustIf ( - justIf, justWhen, soJust + justIf ) where -- | Return @Just@ if True justIf :: a -> Bool -> Maybe a x `justIf` True = Just x _ `justIf` False = Nothing - --- | Return @Just@ if @f x = True@ -justWhen :: a -> (a -> Bool) -> Maybe a -x `justWhen` p = x `justIf` p x - --- | Flipped version of @justIf@ -soJust :: Bool -> a -> Maybe a -soJust = flip justIf
src/HsDev.hs view
@@ -1,33 +1,31 @@-module HsDev (- module Data.Default,-- module HsDev.Types,- module HsDev.Error,- module HsDev.Server.Base,- module HsDev.Server.Commands,- module HsDev.Client.Commands,- module HsDev.Cache,- module HsDev.Commands,- module HsDev.Database,- module HsDev.Inspect,- module HsDev.Project,- module HsDev.Scan,- module HsDev.Symbols,- module HsDev.Util- ) where--import Data.Default--import HsDev.Types-import HsDev.Error-import HsDev.Server.Base-import HsDev.Server.Commands-import HsDev.Client.Commands-import HsDev.Cache-import HsDev.Commands-import HsDev.Database-import HsDev.Inspect-import HsDev.Project-import HsDev.Scan-import HsDev.Symbols-import HsDev.Util+module HsDev ( + module Data.Default, + module System.Directory.Paths, + + module HsDev.Types, + module HsDev.Error, + module HsDev.Server.Base, + module HsDev.Server.Commands, + module HsDev.Client.Commands, + module HsDev.Inspect, + module HsDev.Project, + module HsDev.Scan, + module HsDev.Symbols, + module HsDev.Symbols.Resolve, + module HsDev.Util + ) where + +import Data.Default +import System.Directory.Paths + +import HsDev.Types +import HsDev.Error +import HsDev.Server.Base +import HsDev.Server.Commands +import HsDev.Client.Commands +import HsDev.Inspect +import HsDev.Project +import HsDev.Scan +import HsDev.Symbols +import HsDev.Symbols.Resolve +import HsDev.Util
− src/HsDev/Cache.hs
@@ -1,79 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HsDev.Cache (- escapePath,- versionCache,- packageDbCache,- projectCache,- standaloneCache,- dump,- load,- writeVersion,- readVersion,-- -- * Reexports- Database- ) where--import Control.DeepSeq (force)-import Control.Exception-import Control.Lens (view)-import Data.Aeson (encode, eitherDecode)-import Data.Aeson.Encode.Pretty (encodePretty)-import qualified Data.ByteString.Lazy.Char8 as BS-import Data.Char (isAlphaNum)-import Data.List (intercalate)-import System.FilePath--import HsDev.PackageDb-import HsDev.Project-import HsDev.Database (Database)-import HsDev.Util (version)---- | Escape path-escapePath :: FilePath -> FilePath-escapePath = intercalate "." . map (filter isAlphaNum) . splitDirectories---- | Name of cache for version-versionCache :: FilePath-versionCache = "version" <.> "json"---- | Name of cache for cabal-packageDbCache :: PackageDb -> FilePath-packageDbCache GlobalDb = "global" <.> "json"-packageDbCache UserDb = "user" <.> "json"-packageDbCache (PackageDb p) = escapePath p <.> "json"---- | Name of cache for projects-projectCache :: Project -> FilePath-projectCache p = (escapePath . view projectPath $ p) <.> "json"---- | Name of cache for standalone files-standaloneCache :: FilePath-standaloneCache = "standalone" <.> "json"---- | Dump database to file-dump :: FilePath -> Database -> IO ()-dump file = BS.writeFile file . encodePretty---- | Load database from file, strict-load :: FilePath -> IO (Either String Database)-load file = handle onIO $ do- cts <- BS.readFile file- return $ force $ eitherDecode cts- where- onIO :: IOException -> IO (Either String Database)- onIO _ = return $ Left $ "IO exception while reading cache from " ++ file---- | Write version-writeVersion :: FilePath -> IO ()-writeVersion file = BS.writeFile file $ encode version---- | Read version-readVersion :: FilePath -> IO (Maybe [Int])-readVersion file = handle onIO $ do- cts <- BS.readFile file- return $ either (const Nothing) id $ eitherDecode cts- where- onIO :: IOException -> IO (Maybe [Int])- onIO _ = return Nothing
− src/HsDev/Cache/Structured.hs
@@ -1,72 +0,0 @@-module HsDev.Cache.Structured (- dump, load,- loadPackageDb, loadProject, loadFiles- ) where--import Control.DeepSeq-import Control.Exception-import Control.Lens (preview)-import Control.Monad.Except-import qualified Data.Map as M (assocs)-import System.Directory-import System.FilePath--import Data.Group (Group(zero))-import qualified HsDev.Cache as Cache-import HsDev.Database-import HsDev.Symbols hiding (loadProject)-import HsDev.Util---- | Write cache-dump :: FilePath -> Structured -> IO ()-dump dir db = do- createDirectoryIfMissing True (dir </> "cabal")- createDirectoryIfMissing True (dir </> "projects")- forM_ (M.assocs $ structuredPackageDbs db) $ \(c, cdb) -> Cache.dump- (dir </> "cabal" </> Cache.packageDbCache c)- cdb- forM_ (M.assocs $ structuredProjects db) $ \(p, pdb) -> Cache.dump- (dir </> "projects" </> Cache.projectCache (project p))- pdb- files' <- either (const zero) id <$>- handle wrapIO- (Cache.load (dir </> Cache.standaloneCache))- files' `deepseq` Cache.dump (dir </> Cache.standaloneCache) (files' `mappend` structuredFiles db)- where- wrapIO :: SomeException -> IO (Either String Database)- wrapIO = return . Left . show---- | Load all cache-load :: FilePath -> IO (Either String Structured)-load dir = runExceptT $ join $ either throwError return <$> (structured <$> loadPackageDbs <*> loadProjects <*> loadStandaloneFiles) where- loadPackageDbs = loadDir (dir </> "cabal")- loadProjects = loadDir (dir </> "projects")- loadStandaloneFiles = ExceptT $ Cache.load (dir </> Cache.standaloneCache)-- loadDir p = do- fs <- liftE $ liftM (filter ((== ".json") . takeExtension)) $ directoryContents p- mapM (ExceptT . Cache.load) fs---- | Load data from cache-loadData :: FilePath -> ExceptT String IO Database-loadData = liftExceptionM . ExceptT . Cache.load---- | Load cabal from cache-loadPackageDb :: PackageDb -> FilePath -> ExceptT String IO Structured-loadPackageDb c dir = do- dat <- loadData (dir </> "cabal" </> Cache.packageDbCache c)- ExceptT $ return $ structured [dat] [] mempty---- | Load project from cache-loadProject :: FilePath -> FilePath -> ExceptT String IO Structured-loadProject p dir = do- dat <- loadData (dir </> "projects" </> Cache.projectCache (project p))- ExceptT $ return $ structured [] [dat] mempty---- | Load standalone files-loadFiles :: (FilePath -> Bool) -> FilePath -> ExceptT String IO Structured-loadFiles f dir = do- dat <- loadData (dir </> Cache.standaloneCache)- ExceptT $ return $ structured [] [] $ filterDB f' (const False) dat- where- f' = maybe False f . preview (moduleIdLocation . moduleFile)
src/HsDev/Client/Commands.hs view
@@ -1,414 +1,622 @@-{-# LANGUAGE OverloadedStrings, FlexibleContexts #-}--module HsDev.Client.Commands (- runClient, runCommand- ) where--import Control.Applicative-import Control.Concurrent.MVar-import Control.Exception (displayException)-import Control.Lens (view, preview, _Just, (^..), each)-import Control.Monad-import Control.Monad.Except-import Control.Monad.Reader-import qualified Control.Monad.State as State-import Control.Monad.Catch (try, catch, bracket, SomeException(..))-import Data.Aeson hiding (Result, Error)-import Data.List-import Data.Foldable (toList)-import Data.Maybe-import qualified Data.Map as M-import Data.String (fromString)-import Data.Text (pack, unpack)-import qualified Data.Text as T (isInfixOf, isPrefixOf, isSuffixOf)-import System.Directory-import System.FilePath-import qualified System.Log.Simple as Log-import qualified System.Log.Simple.Base as Log-import Text.Regex.PCRE ((=~))--import qualified System.Directory.Watcher as W-import qualified Data.Async as A-import System.Directory.Paths-import Text.Format-import HsDev.Cache-import HsDev.Commands-import HsDev.Error-import qualified HsDev.Database.Async as DB-import HsDev.Server.Message as M-import HsDev.Server.Types-import HsDev.Sandbox hiding (findSandbox)-import qualified HsDev.Sandbox as S (findSandbox)-import HsDev.Symbols-import HsDev.Symbols.Resolve (resolveOne, scopeModule, exportsModule)-import HsDev.Symbols.Util-import qualified HsDev.Tools.AutoFix as AutoFix-import qualified HsDev.Tools.Cabal as Cabal-import HsDev.Tools.Ghc.Session-import qualified HsDev.Tools.Ghc.Compat as Compat-import qualified HsDev.Tools.Ghc.Check as Check-import qualified HsDev.Tools.Ghc.Types as Types-import qualified HsDev.Tools.Hayoo as Hayoo-import qualified HsDev.Tools.HLint as HLint-import qualified HsDev.Tools.Types as Tools-import HsDev.Util-import HsDev.Watcher--import qualified HsDev.Database.Update as Update--runClient :: (ToJSON a, ServerMonadBase m) => CommandOptions -> ClientM m a -> ServerM m Result-runClient copts = mapServerM toResult . runClientM where- toResult :: (ToJSON a, ServerMonadBase m) => ReaderT CommandOptions m a -> m Result- toResult act = liftM (either Error (Result . toJSON)) $ runReaderT (try act) copts- mapServerM :: (m a -> n b) -> ServerM m a -> ServerM n b- mapServerM f = ServerM . mapReaderT f . runServerM--toValue :: (ToJSON a, Monad m) => m a -> m Value-toValue = liftM toJSON--runCommand :: ServerMonadBase m => Command -> ClientM m Value-runCommand Ping = toValue $ return $ object ["message" .= ("pong" :: String)]-runCommand (Listen (Just l)) = case Log.level (pack l) of- Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l- Just lev -> bracket (serverSetLogLevel lev) serverSetLogLevel $ \_ -> runCommand (Listen Nothing)-runCommand (Listen Nothing) = toValue $ do- serverListen >>= mapM_ (\msg -> commandNotify (Notification $ object ["message" .= msg]))-runCommand (SetLogLevel l) = case Log.level (pack l) of- Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l- Just lev -> toValue $ do- lev' <- serverSetLogLevel lev- Log.sendLog Log.Debug $ "log level changed from '{}' to '{}'" ~~ show lev' ~~ show lev- Log.sendLog Log.Info $ "log level updated to: {}" ~~ show lev-runCommand (AddData cts) = toValue $ mapM_ updateData cts where- updateData (AddedDatabase db) = toValue $ serverUpdateDB db- updateData (AddedModule m) = toValue $ serverUpdateDB $ fromModule m- updateData (AddedProject p) = toValue $ serverUpdateDB $ fromProject p-runCommand (Scan projs cabal sboxes fs paths' ghcs' docs' infer') = toValue $ do- sboxes' <- getSandboxes sboxes- updateProcess (Update.UpdateOptions [] ghcs' docs' infer') $ concat [- map (\(FileSource f mcts) -> Update.scanFileContents ghcs' f mcts) fs,- map (Update.scanProject ghcs') projs,- map (Update.scanDirectory ghcs') paths',- [Update.scanCabal ghcs' | cabal],- map (Update.scanSandbox ghcs') sboxes']-runCommand (RefineDocs projs fs ms) = toValue $ do- projects <- traverse findProject projs- dbval <- getDb- let- filters = anyOf $ concat [- map inProject projects,- map inFile fs,- map inModule ms]- mods = selectModules (filters . view moduleId) dbval- updateProcess (Update.UpdateOptions [] [] False False) [Update.scanDocs $ map (getInspected dbval) mods]-runCommand (InferTypes projs fs ms) = toValue $ do- projects <- traverse findProject projs- dbval <- getDb- let- filters = anyOf $ concat [- map inProject projects,- map inFile fs,- map inModule ms]- mods = selectModules (filters . view moduleId) dbval- updateProcess (Update.UpdateOptions [] [] False False) [Update.inferModTypes $ map (getInspected dbval) mods]-runCommand (Remove projs cabal sboxes files) = toValue $ do- db <- askSession sessionDatabase- dbval <- getDb- w <- askSession sessionWatcher- projects <- traverse findProject projs- sboxes' <- getSandboxes sboxes- forM_ projects $ \proj -> do- DB.clear db (return $ projectDB proj dbval)- liftIO $ unwatchProject w proj- dbPDbs <- mapM restorePackageDbStack $ databasePackageDbs dbval- flip State.evalStateT dbPDbs $ do- when cabal $ removePackageDbStack userDb- forM_ sboxes' $ \sbox -> do- pdbs <- lift $ sandboxPackageDbStack sbox- removePackageDbStack pdbs- forM_ files $ \file -> do- DB.clear db (return $ filterDB (inFile file) (const False) dbval)- let- mloc = fmap (view moduleLocation) $ lookupFile file dbval- maybe (return ()) (liftIO . unwatchModule w) mloc- where- -- We can safely remove package-db from db iff doesn't used by some of other package-dbs- -- For example, we can't remove global-db if there are any other package-dbs, because all of them uses global-db- -- We also can't remove stack snapshot package-db if there are some local package-db not yet removed- canRemove pdbs = do- from <- State.get- return $ null $ filter (pdbs `isSubStack`) $ delete pdbs from- -- Remove top of package-db stack if possible- removePackageDb pdbs = do- db <- lift $ askSession sessionDatabase- dbval <- lift getDb- w <- lift $ askSession sessionWatcher- can <- canRemove pdbs- when can $ do- State.modify (delete pdbs)- DB.clear db (return $ packageDbDB (topPackageDb pdbs) dbval)- liftIO $ unwatchPackageDb w $ topPackageDb pdbs- -- Remove package-db stack when possible- removePackageDbStack = mapM_ removePackageDb . packageDbStacks-runCommand RemoveAll = toValue $ do- db <- askSession sessionDatabase- liftIO $ A.modifyAsync db A.Clear- w <- askSession sessionWatcher- wdirs <- liftIO $ readMVar (W.watcherDirs w)- liftIO $ forM_ (M.toList wdirs) $ \(dir, (isTree, _)) -> (if isTree then W.unwatchTree else W.unwatchDir) w dir-runCommand (InfoModules fs) = toValue $ do- dbval <- getDb- filter' <- targetFilters fs- return $ map (view moduleId) $ newestPackage $ selectModules (filter' . view moduleId) dbval-runCommand InfoPackages = toValue $ (ordNub . sort . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules) <$> getDb-runCommand InfoProjects = toValue $ (toList . databaseProjects) <$> getDb-runCommand InfoSandboxes = toValue $ databasePackageDbs <$> getDb-runCommand (InfoSymbol sq fs locals') = toValue $ do- dbval <- liftM (localsDatabase locals') $ getDb- filter' <- targetFilters fs- return $ newestPackage $ filterMatch sq $- concatMap moduleModuleDeclarations $ filter (filter' . view moduleId) $ allModules dbval-runCommand (InfoModule sq fs) = toValue $ do- dbval <- getDb- filter' <- targetFilters fs- return $ newestPackage $ filterMatch sq $ filter (filter' . view moduleId) $ allModules dbval-runCommand (InfoResolve fpath exports) = toValue $ do- dbval <- getSDb fpath- let- getScope- | exports = exportsModule- | otherwise = scopeModule- m <- refineSourceModule fpath- return $ getScope $ resolveOne dbval m-runCommand (InfoProject (Left projName)) = toValue $ findProject projName-runCommand (InfoProject (Right projPath)) = toValue $ liftIO $ searchProject projPath-runCommand (InfoSandbox sandbox') = toValue $ liftIO $ searchSandbox sandbox'-runCommand (Lookup nm fpath) = toValue $ do- dbval <- getSDb fpath- liftIO $ hsdevLift $ lookupSymbol dbval fpath nm-runCommand (Whois nm fpath) = toValue $ do- dbval <- getSDb fpath- liftIO $ hsdevLift $ whois dbval fpath nm-runCommand (ResolveScopeModules sq fpath) = toValue $ do- dbval <- getSDb fpath- liftM (filterMatch sq . map (view moduleId)) $ liftIO $ hsdevLift $ scopeModules dbval fpath-runCommand (ResolveScope sq global fpath) = toValue $ do- dbval <- getSDb fpath- liftM (filterMatch sq) $ liftIO $ hsdevLift $ scope dbval fpath global-runCommand (Complete input wide fpath) = toValue $ do- dbval <- getSDb fpath- liftIO $ hsdevLift $ completions dbval fpath input wide-runCommand (Hayoo hq p ps) = toValue $ liftM concat $ forM [p .. p + pred ps] $ \i -> liftM- (mapMaybe Hayoo.hayooAsDeclaration . Hayoo.resultResult) $- liftIO $ hsdevLift $ Hayoo.hayoo hq (Just i)-runCommand (CabalList packages) = toValue $ liftIO $ hsdevLift $ Cabal.cabalList packages-runCommand (Lint fs) = toValue $ do- liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint f c) fs-runCommand (Check fs ghcs') = toValue $ Log.scope "check" $ do- -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs- let- checkSome file fn = Log.scope "checkSome" $ do- m <- setFileSourceSession ghcs' file- inSessionGhc $ fn m- liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs-runCommand (CheckLint fs ghcs') = toValue $ do- -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs- let- checkSome file fn = Log.scope "checkSome" $ do- m <- setFileSourceSession ghcs' file- inSessionGhc $ fn m- checkMsgs <- liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs- lintMsgs <- liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint f c) fs- return $ checkMsgs ++ lintMsgs-runCommand (Types fs ghcs') = toValue $ do- -- ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs- liftM concat $ forM fs $ \(FileSource file msrc) -> do- m <- setFileSourceSession ghcs' file- inSessionGhc $ Types.fileTypes ghcs' m msrc-runCommand (AutoFix (AutoFixShow ns)) = toValue $ return $ AutoFix.corrections ns-runCommand (AutoFix (AutoFixFix ns rest isPure)) = toValue $ do- files <- liftM (ordNub . sort) $ mapM findPath $ mapMaybe (preview $ Tools.noteSource . moduleFile) ns- let- doFix :: FilePath -> Maybe String -> ([Tools.Note AutoFix.Correction], Maybe String)- doFix file mcts = AutoFix.autoFix fCorrs (fUpCorrs, mcts) where- findCorrs :: FilePath -> [Tools.Note AutoFix.Correction] -> [Tools.Note AutoFix.Correction]- findCorrs f = filter ((== Just f) . preview (Tools.noteSource . moduleFile))- fCorrs = findCorrs file ns- fUpCorrs = findCorrs file rest- runFix file- | isPure = return $ fst $ doFix file Nothing- | otherwise = do- (corrs', Just cts') <- liftM (doFix file) $ liftIO $ Just <$> readFileUtf8 file- liftIO $ writeFileUtf8 file cts'- return corrs'- liftM concat $ mapM runFix files-runCommand (GhcEval exprs mfile) = toValue $ do- -- ensureUpToDate (Update.UpdateOptions [] [] False False) (maybeToList mfile)- ghcw <- askSession sessionGhc- case mfile of- Nothing -> inSessionGhc ghciSession- Just (FileSource f mcts) -> do- m <- setFileSourceSession [] f- inSessionGhc $ interpretModule m mcts- async' <- liftIO $ pushTask ghcw $ do- mapM (try . evaluate) exprs- res <- waitAsync async'- return $ map toValue' res- where- waitAsync :: CommandMonad m => Async a -> m a- waitAsync a = liftIO (waitCatch a) >>= either (hsdevError . GhcError . displayException) return- toValue' :: ToJSON a => Either SomeException a -> Value- toValue' (Left (SomeException e)) = object ["fail" .= show e]- toValue' (Right s) = toJSON s-runCommand Langs = toValue $ return $ Compat.languages-runCommand Flags = toValue $ return ["-f" ++ prefix ++ f |- f <- Compat.flags,- prefix <- ["", "no-"]]-runCommand (Link hold) = toValue $ commandLink >> when hold commandHold-runCommand Exit = toValue serverExit--targetFilters :: CommandMonad m => [TargetFilter] -> m (ModuleId -> Bool)-targetFilters fs = do- fs_ <- mapM targetFilter fs- return $ foldr (liftM2 (&&)) (const True) fs_--targetFilter :: CommandMonad m => TargetFilter -> m (ModuleId -> Bool)-targetFilter f = case f of- TargetProject proj -> liftM inProject $ findProject proj- TargetFile file -> liftM inFile $ refineSourceFile file- TargetModule mname -> return $ inModule mname- TargetDepsOf dep -> liftM inDeps $ findDep dep- TargetPackageDb pdb -> return $ inPackageDb pdb- TargetCabal -> return $ inPackageDbStack userDb- TargetSandbox sbox -> liftM inPackageDbStack $ findSandbox sbox >>= sandboxPackageDbStack- TargetPackage pkg -> liftM inPackage $ refinePackage pkg- TargetSourced -> return byFile- TargetStandalone -> return standalone---- Helper functions---- | Canonicalize paths-findPath :: (CommandMonad m, Paths a) => a -> m a-findPath = paths findPath' where- findPath' :: CommandMonad m => FilePath -> m FilePath- findPath' f = do- r <- commandRoot- liftIO $ canonicalizePath (normalise $ if isRelative f then r </> f else f)---- | Find sandbox by path-findSandbox :: CommandMonad m => FilePath -> m Sandbox-findSandbox fpath = do- fpath' <- findPath fpath- sbox <- liftIO $ S.findSandbox fpath'- maybe (hsdevError $ FileNotFound fpath') return sbox---- | Get source file-refineSourceFile :: CommandMonad m => FilePath -> m FilePath-refineSourceFile fpath = do- fpath' <- findPath fpath- db' <- getDb- maybe (hsdevError (NotInspected $ FileModule fpath' Nothing)) return $ do- m' <- lookupFile fpath' db'- preview (moduleLocation . moduleFile) m'---- | Get module by source-refineSourceModule :: CommandMonad m => FilePath -> m Module-refineSourceModule fpath = do- fpath' <- findPath fpath- db' <- getDb- maybe (hsdevError (NotInspected $ FileModule fpath' Nothing)) return $ lookupFile fpath' db'---- | Set session by source-setFileSourceSession :: CommandMonad m => [String] -> FilePath -> m Module-setFileSourceSession opts fpath = do- m <- refineSourceModule fpath- inSessionGhc $ targetSession opts m- return m---- | Ensure package exists-refinePackage :: CommandMonad m => String -> m String-refinePackage pkg = do- db' <- getDb- if pkg `elem` (allPackages db' ^.. each . packageName)- then return pkg- else hsdevError (PackageNotFound pkg)---- | Get list of enumerated sandboxes-getSandboxes :: CommandMonad m => [FilePath] -> m [Sandbox]-getSandboxes = traverse (findPath >=> findSandbox)---- | Find project by name or path-findProject :: CommandMonad m => String -> m Project-findProject proj = do- db' <- getDb- proj' <- liftM addCabal $ findPath proj- let- resultProj =- refineProject db' (project proj') <|>- find ((== proj) . view projectName) (databaseProjects db')- maybe (hsdevError $ ProjectNotFound proj) return resultProj- where- addCabal p- | takeExtension p == ".cabal" = p- | otherwise = p </> (takeBaseName p <.> "cabal")---- | Find dependency: it may be source, project file or project name, also returns sandbox found-findDep :: CommandMonad m => String -> m (Project, Maybe FilePath, PackageDbStack)-findDep depName = do- depPath <- findPath depName- proj <- msum [- do- p <- liftIO (locateProject depPath)- p' <- maybe (hsdevError $ ProjectNotFound depName) return p- liftIO $ loadProject p',- findProject depName]- let- src- | takeExtension depPath == ".hs" = Just depPath- | otherwise = Nothing- pdbs <- searchPackageDbStack $ view projectPath proj- return (proj, src, pdbs)---- FIXME: Doesn't work for file without project--- | Check if project or source depends from this module-inDeps :: (Project, Maybe FilePath, PackageDbStack) -> ModuleId -> Bool-inDeps (proj, src, pdbs) = liftM2 (&&) (restrictPackageDbStack pdbs) deps' where- deps' = case src of- Nothing -> inDepsOfProject proj- Just src' -> inDepsOfFile proj src'---- | Bring locals to top scope to search within them if 'locals' flag set-localsDatabase :: Bool -> Database -> Database-localsDatabase True = databaseLocals-localsDatabase False = id---- | Get actual DB state-getDb :: SessionMonad m => m Database-getDb = askSession sessionDatabase >>= liftIO . DB.readAsync---- | Get DB with filtered sanxboxes for file-getSDb :: SessionMonad m => FilePath -> m Database-getSDb fpath = do- dbval <- getDb- pdbs <- searchPackageDbStack fpath- return $ filterDB (restrictPackageDbStack pdbs) (const True) dbval---- | Run DB update action-updateProcess :: ServerMonadBase m => Update.UpdateOptions -> [Update.UpdateM m ()] -> ClientM m ()-updateProcess uopts acts = Update.runUpdate uopts $ mapM_ runAct acts where- runAct act = catch act onError- onError e = Log.sendLog Log.Error $ "{}" ~~ (e :: HsDevError)---- | Ensure file is up to date--- ensureUpToDate :: ServerMonadBase m => Update.UpdateOptions -> [FileSource] -> ClientM m ()--- ensureUpToDate uopts fs = updateProcess uopts [Update.scanFileContents (view Update.updateGhcOpts uopts) f mcts | FileSource f mcts <- fs]---- | Filter declarations with prefix and infix-filterMatch :: Symbol a => SearchQuery -> [a] -> [a]-filterMatch (SearchQuery q st) = filter match' where- match' m = case st of- SearchExact -> fromString q == symbolName m- SearchPrefix -> fromString q `T.isPrefixOf` symbolName m- SearchInfix -> fromString q `T.isInfixOf` symbolName m- SearchSuffix -> fromString q `T.isSuffixOf` symbolName m- SearchRegex -> unpack (symbolName m) =~ q+{-# LANGUAGE OverloadedStrings, FlexibleContexts, TypeOperators, TypeApplications #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +module HsDev.Client.Commands ( + runClient, runCommand + ) where + +import Control.Arrow (second) +import Control.Concurrent.MVar +import Control.Exception (displayException) +import Control.Lens hiding ((.=), (<.>)) +import Control.Monad +import Control.Monad.Morph +import Control.Monad.Except +import Control.Monad.Reader +import qualified Control.Monad.State as State +import Control.Monad.Catch (try, catch, bracket, SomeException(..)) +import Data.Aeson hiding (Result, Error) +import Data.List +import Data.Maybe +import qualified Data.Map.Strict as M +import Data.Text (Text, pack, unpack) +import qualified Data.Text as T (append) +import System.Directory +import System.FilePath +import qualified System.Log.Simple as Log +import qualified System.Log.Simple.Base as Log +import Text.Read (readMaybe) + +import qualified System.Directory.Watcher as W +import System.Directory.Paths +import Text.Format +import HsDev.Error +import HsDev.Database.SQLite as SQLite +import HsDev.Inspect (preload, asModule) +import HsDev.Scan (upToDate, getFileContents) +import HsDev.Server.Message as M +import HsDev.Server.Types +import HsDev.Sandbox hiding (findSandbox) +import qualified HsDev.Sandbox as S (findSandbox) +import HsDev.Symbols +import qualified HsDev.Tools.AutoFix as AutoFix +import qualified HsDev.Tools.Cabal as Cabal +import HsDev.Tools.Ghc.Session +import HsDev.Tools.Ghc.Worker (clearTargets) +import qualified HsDev.Tools.Ghc.Compat as Compat +import qualified HsDev.Tools.Ghc.Check as Check +import qualified HsDev.Tools.Ghc.Types as Types +import qualified HsDev.Tools.Hayoo as Hayoo +import qualified HsDev.Tools.HDocs as HDocs +import qualified HsDev.Tools.HLint as HLint +import qualified HsDev.Tools.Types as Tools +import HsDev.Util +import HsDev.Watcher + +import qualified HsDev.Database.Update as Update + +runClient :: (ToJSON a, ServerMonadBase m) => CommandOptions -> ClientM m a -> ServerM m Result +runClient copts = mapServerM toResult . runClientM where + toResult :: (ToJSON a, ServerMonadBase m) => ReaderT CommandOptions m a -> m Result + toResult act = liftM errorToResult $ runReaderT (try (try act)) copts + mapServerM :: (m a -> n b) -> ServerM m a -> ServerM n b + mapServerM f = ServerM . mapReaderT f . runServerM + errorToResult :: ToJSON a => Either SomeException (Either HsDevError a) -> Result + errorToResult = either (Error . UnhandledError . displayException) (either Error (Result . toJSON)) + +toValue :: (ToJSON a, Monad m) => m a -> m Value +toValue = liftM toJSON + +runCommand :: ServerMonadBase m => Command -> ClientM m Value +runCommand Ping = toValue $ return $ object ["message" .= ("pong" :: String)] +runCommand (Listen (Just l)) = case Log.level (pack l) of + Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l + Just lev -> bracket (serverSetLogLevel lev) serverSetLogLevel $ \_ -> runCommand (Listen Nothing) +runCommand (Listen Nothing) = toValue $ do + serverListen >>= mapM_ (commandNotify . Notification . toJSON) +runCommand (SetLogLevel l) = case Log.level (pack l) of + Nothing -> hsdevError $ OtherError $ "invalid log level: {}" ~~ l + Just lev -> toValue $ do + lev' <- serverSetLogLevel lev + Log.sendLog Log.Debug $ "log level changed from '{}' to '{}'" ~~ show lev' ~~ show lev + Log.sendLog Log.Info $ "log level updated to: {}" ~~ show lev +runCommand (Scan projs cabal sboxes fs paths' ghcs' docs' infer') = toValue $ do + sboxes' <- getSandboxes sboxes + updateProcess (Update.UpdateOptions [] ghcs' docs' infer') $ concat [ + [Update.scanCabal ghcs' | cabal], + map (Update.scanSandbox ghcs') sboxes', + [Update.scanFiles (zip fs (repeat ghcs'))], + map (Update.scanProject ghcs') projs, + map (Update.scanDirectory ghcs') paths'] +runCommand (SetFileContents f mcts) = toValue $ serverSetFileContents f mcts +runCommand (RefineDocs projs fs) + | HDocs.hdocsSupported = toValue $ do + projects <- traverse findProject projs + mods <- do + projMods <- liftM concat $ forM projects $ \proj -> do + ms <- loadModules "select id from modules where cabal == ? and json_extract(tags, '$.docs') is null" + (Only $ proj ^. projectCabal) + p <- SQLite.loadProject (proj ^. projectCabal) + return $ set (each . moduleId . moduleLocation . moduleProject) (Just p) ms + fileMods <- liftM concat $ forM fs $ \f -> + loadModules "select id from modules where file == ? and json_extract(tags, '$.docs') is null" + (Only f) + return $ projMods ++ fileMods + updateProcess def [Update.scanDocs mods] + | otherwise = hsdevError $ OtherError "docs not supported" +runCommand (InferTypes projs fs) = toValue $ do + projects <- traverse findProject projs + mods <- do + projMods <- liftM concat $ forM projects $ \proj -> do + ms <- loadModules "select id from modules where cabal == ? and json_extract(tags, '$.types') is null" + (Only $ proj ^. projectCabal) + p <- SQLite.loadProject (proj ^. projectCabal) + return $ set (each . moduleId . moduleLocation . moduleProject) (Just p) ms + fileMods <- liftM concat $ forM fs $ \f -> + loadModules "select id from modules where file == ? and json_extract(tags, '$.types') is null" + (Only f) + return $ projMods ++ fileMods + updateProcess def [Update.inferModTypes mods] +runCommand (Remove projs cabal sboxes files) = toValue $ withSqlConnection $ SQLite.transaction_ SQLite.Immediate $ do + let + -- We can safely remove package-db from db iff doesn't used by some of other package-dbs + -- For example, we can't remove global-db if there are any other package-dbs, because all of them uses global-db + -- We also can't remove stack snapshot package-db if there are some local package-db not yet removed + canRemove pdbs = do + from' <- State.get + return $ null $ filter (pdbs `isSubStack`) $ delete pdbs from' + -- Remove top of package-db stack if possible + removePackageDb' pdbs = do + w <- lift $ askSession sessionWatcher + can <- canRemove pdbs + when can $ do + State.modify (delete pdbs) + ms <- liftM (map fromOnly) $ query_ + "select m.id from modules as m, package_dbs as ps where m.package_name == ps.package_name and m.package_version == ps.package_version;" + removePackageDb (topPackageDb pdbs) + mapM_ SQLite.removeModule ms + liftIO $ unwatchPackageDb w $ topPackageDb pdbs + -- Remove package-db stack when possible + removePackageDbStack = mapM_ removePackageDb' . packageDbStacks + w <- askSession sessionWatcher + projects <- traverse findProject projs + sboxes' <- getSandboxes sboxes + forM_ projects $ \proj -> do + ms <- liftM (map fromOnly) $ query "select id from modules where cabal == ?;" (Only $ proj ^. projectCabal) + SQLite.removeProject proj + mapM_ SQLite.removeModule ms + liftIO $ unwatchProject w proj + + allPdbs <- liftM (map fromOnly) $ query_ @(Only PackageDb) "select package_db from package_dbs;" + dbPDbs <- inSessionGhc $ mapM restorePackageDbStack allPdbs + flip State.evalStateT dbPDbs $ do + when cabal $ removePackageDbStack userDb + forM_ sboxes' $ \sbox -> do + pdbs <- lift $ inSessionGhc $ sandboxPackageDbStack sbox + removePackageDbStack pdbs + + forM_ files $ \file -> do + ms <- query @_ @(ModuleId :. Only Int) (toQuery $ qModuleId `mappend` select_ ["mu.id"] [] ["mu.file == ?"]) (Only file) + forM_ ms $ \(m :. Only i) -> do + SQLite.removeModule i + liftIO . unwatchModule w $ (m ^. moduleLocation) +runCommand RemoveAll = toValue $ do + SQLite.purge + w <- askSession sessionWatcher + wdirs <- liftIO $ readMVar (W.watcherDirs w) + liftIO $ forM_ (M.toList wdirs) $ \(dir, (isTree, _)) -> (if isTree then W.unwatchTree else W.unwatchDir) w dir +runCommand InfoPackages = toValue $ + query_ @ModulePackage "select package_name, package_version from package_dbs;" +runCommand InfoProjects = toValue $ do + ps <- query_ @(Only Int :. Project) "select p.id, p.name, p.cabal, p.version from projects as p;" + forM ps $ \(Only pid :. proj) -> do + libs <- query @_ @Library "select l.modules, b.depends, b.language, b.extensions, b.ghc_options, b.source_dirs, b.other_modules from libraries as l, build_infos as b where (l.project_id == ?) and (l.build_info_id == b.id);" + (Only pid) + exes <- query @_ @Executable "select e.name, e.path, b.depends, b.language, b.extensions, b.ghc_options, b.source_dirs, b.other_modules from executables as e, build_infos as b where (e.project_id == ?) and (e.build_info_id == b.id);" + (Only pid) + tsts <- query @_ @Test "select t.name, t.enabled, t.main, b.depends, b.language, b.extensions, b.ghc_options, b.source_dirs, b.other_modules from tests as t, build_infos as b where (t.project_id == ?) and (t.build_info_id == b.id);" + (Only pid) + return $ + set (projectDescription . _Just . projectLibrary) (listToMaybe libs) . + set (projectDescription . _Just . projectExecutables) exes . + set (projectDescription . _Just . projectTests) tsts $ + proj +runCommand InfoSandboxes = toValue $ do + rs <- query_ @(Only PackageDb) "select distinct package_db from package_dbs;" + return [pdb | Only pdb <- rs] +runCommand (InfoSymbol sq filters True _) = toValue $ do + let + (conds, params) = targetFilters "m" (Just "s") filters + rs <- queryNamed @SymbolId (toQuery $ qSymbolId `mappend` where_ ["s.name like :pattern escape '\\'"] `mappend` where_ conds) + ([":pattern" := likePattern sq] ++ params) + return rs +runCommand (InfoSymbol sq filters False _) = toValue $ do + let + (conds, params) = targetFilters "m" (Just "s") filters + queryNamed @Symbol (toQuery $ qSymbol `mappend` where_ ["s.name like :pattern escape '\\'"] `mappend` where_ conds) + ([":pattern" := likePattern sq] ++ params) +runCommand (InfoModule sq filters h _) = toValue $ do + let + (conds, params) = targetFilters "mu" Nothing filters + rs <- queryNamed @(Only Int :. ModuleId) (toQuery $ select_ ["mu.id"] [] [] `mappend` qModuleId `mappend` where_ ["mu.name like :pattern escape '\\'"] `mappend` where_ conds) + ([":pattern" := likePattern sq] ++ params) + if h + then return (toJSON $ map (\(_ :. m) -> m) rs) + else liftM toJSON $ forM rs $ \(Only mid :. mheader) -> do + [(docs, fixities)] <- query @_ @(Maybe Text, Maybe Value) "select m.docs, m.fixities from modules as m where (m.id == ?);" + (Only mid) + let + fixities' = fromMaybe [] (fixities >>= fromJSON') + exports' <- query @_ @Symbol (toQuery $ qSymbol `mappend` select_ [] + ["exports as e"] + ["e.module_id == ?", "e.symbol_id == s.id"]) + (Only mid) + return $ Module mheader docs mempty exports' fixities' mempty Nothing +runCommand (InfoProject (Left projName)) = toValue $ findProject projName +runCommand (InfoProject (Right projPath)) = toValue $ liftIO $ searchProject (view path projPath) +runCommand (InfoSandbox sandbox') = toValue $ liftIO $ searchSandbox sandbox' +runCommand (Lookup nm fpath) = toValue $ + query @_ @Symbol (toQuery $ qSymbol `mappend` select_ [] + ["projects_modules_scope as pms", "modules as srcm", "exports as e"] + [ + "pms.cabal is srcm.cabal", + "srcm.file == ?", + "pms.module_id == e.module_id", + "m.id == s.module_id", + "s.id == e.symbol_id", + "s.name == ?"]) + (fpath ^. path, nm) +runCommand (Whois nm fpath) = toValue $ do + let + q = nameModule $ toName nm + ident = nameIdent $ toName nm + query @_ @Symbol (toQuery $ qSymbol `mappend` select_ [] + ["modules as srcm", "scopes as sc"] + [ + "srcm.id == sc.module_id", + "s.id == sc.symbol_id", + "srcm.file == ?", + "sc.qualifier is ?", + "sc.name == ?"]) + (fpath ^. path, q, ident) +runCommand (Whoat l c fpath) = toValue $ do + rs <- query @_ @Symbol (toQuery $ qSymbol `mappend` select_ [] + ["names as n", "modules as srcm"] + [ + "srcm.id == n.module_id", + "m.name == n.resolved_module", + "s.name == n.resolved_name", + "m.id in (select srcm.id union select module_id from projects_modules_scope where (((cabal is null) and (srcm.cabal is null)) or (cabal == srcm.cabal)))", + "srcm.file == ?", + "(?, ?) between (n.line, n.column) and (n.line_to, n.column_to)"]) + (fpath ^. path, l, c) + locals <- do + defs <- query @_ @(ModuleId :. (Text, Int, Int, Maybe Text)) (toQuery $ qModuleId `mappend` select_ + ["n.name", "n.def_line", "n.def_column", "n.inferred_type"] + ["names as n"] + [ + "mu.id == n.module_id", + "n.def_line is not null", + "n.def_column is not null", + "mu.file == ?", + "(?, ?) between (n.line, n.column) and (n.line_to, n.column_to)"]) + (fpath ^. path, l, c) + return [ + Symbol { + _symbolId = SymbolId nm mid, + _symbolDocs = Nothing, + _symbolPosition = Just (Position defLine defColumn), + _symbolInfo = Function ftype + } | (mid :. (nm, defLine, defColumn, ftype)) <- defs] + return $ rs ++ locals +runCommand (ResolveScopeModules sq fpath) = toValue $ do + pids <- query @_ @(Only (Maybe Path)) "select m.cabal from modules as m where (m.file == ?);" + (Only $ fpath ^. path) + case pids of + [] -> hsdevError $ OtherError $ "module at {} not found" ~~ fpath + [Only proj] -> query @_ @ModuleId (toQuery $ qModuleId `mappend` select_ [] + ["projects_modules_scope as msc"] + [ + "msc.module_id == mu.id", + "msc.cabal is ?", + "mu.name like ? escape '\\'"]) + (proj, likePattern sq) + _ -> fail "Impossible happened: several projects for one module" +runCommand (ResolveScope sq fpath) = toValue $ do + rs <- query @_ @(SymbolId :. Only (Maybe Text)) (toQuery $ qSymbolId `mappend` select_ ["sc.qualifier"] + ["scopes as sc", "modules as srcm"] + [ + "srcm.id == sc.module_id", + "sc.symbol_id == s.id", + "srcm.file == ?", + "s.name like ? escape '\\'"]) + (fpath ^. path, likePattern sq) + return [ScopeSymbol q s | (s :. Only q) <- rs] +runCommand (FindUsages nm) = toValue $ do + let + q = nameModule $ toName nm + ident = nameIdent $ toName nm + query @_ @SymbolUsage (toQuery $ qSymbol `mappend` qModuleId `mappend` select_ + ["n.line", "n.column"] + ["names as n"] + [ + "m.name == n.resolved_module", + "s.name == n.resolved_name", + "mu.id == n.module_id", + "n.resolved_module == ? or ? is null", + "n.resolved_name == ?"]) + (q, q, ident) +runCommand (Complete input True fpath) = toValue $ + query @_ @Symbol (toQuery $ qSymbol `mappend` select_ [] + ["modules as srcm"] + [ + "m.id in (select srcm.id union select module_id from projects_modules_scope where (((cabal is null) and (srcm.cabal is null)) or (cabal == srcm.cabal)))", + "msrc.file == ?", + "s.name like ? escape '\\'"]) + (fpath ^. path, likePattern (SearchQuery input SearchPrefix)) +runCommand (Complete input False fpath) = toValue $ + query @_ @Symbol (toQuery $ qSymbol `mappend` select_ [] + ["completions as c", "modules as srcm"] + [ + "c.module_id == srcm.id", + "c.symbol_id == s.id", + "srcm.file == ?", + "c.completion like ? escape '\\'"]) + (fpath ^. path, likePattern (SearchQuery input SearchPrefix)) +runCommand (Hayoo hq p ps) = toValue $ liftM concat $ forM [p .. p + pred ps] $ \i -> liftM + (mapMaybe Hayoo.hayooAsSymbol . Hayoo.resultResult) $ + liftIO $ hsdevLift $ Hayoo.hayoo hq (Just i) +runCommand (CabalList packages') = toValue $ liftIO $ hsdevLift $ Cabal.cabalList $ map unpack packages' +runCommand (UnresolvedSymbols fs) = toValue $ liftM concat $ forM fs $ \f -> do + rs <- query @_ @(Maybe String, String, Int, Int) "select n.qualifier, n.name, n.line, n.column from modules as m, names as n where (m.id == n.module_id) and (m.file == ?) and (n.resolve_error is not null);" + (Only $ f ^. path) + return $ map (\(m, nm, line, column) -> object [ + "qualifier" .= m, + "name" .= nm, + "line" .= line, + "column" .= column]) rs +runCommand (Lint fs) = toValue $ liftM concat $ forM fs $ \fsrc -> do + FileSource f c <- actualFileContents fsrc + liftIO $ hsdevLift $ HLint.hlint (view path f) c +runCommand (Check fs ghcs' clear) = toValue $ Log.scope "check" $ do + let + checkSome file fn = Log.scope "checkSome" $ do + Log.sendLog Log.Trace $ "setting file source session for {}" ~~ file + m <- setFileSourceSession ghcs' file + Log.sendLog Log.Trace $ "file source session set" + inSessionGhc $ do + when clear $ clearTargets + fn m + fs' <- mapM actualFileContents fs + liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check m c)) fs' +runCommand (CheckLint fs ghcs' clear) = toValue $ do + let + checkSome file fn = Log.scope "checkSome" $ do + Log.sendLog Log.Trace $ "setting file source session for {}" ~~ file + m <- setFileSourceSession ghcs' file + Log.sendLog Log.Trace $ "file source session set" + inSessionGhc $ do + when clear $ clearTargets + fn m + fs' <- mapM actualFileContents fs + checkMsgs <- liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check m c)) fs' + lintMsgs <- liftIO $ hsdevLift $ liftM concat $ mapM (\(FileSource f c) -> HLint.hlint (view path f) c) fs' + return $ checkMsgs ++ lintMsgs +runCommand (Types fs ghcs' clear) = toValue $ do + liftM concat $ forM fs $ \fsrc@(FileSource file msrc) -> do + mcached' <- getCached file msrc + FileSource _ msrc' <- actualFileContents fsrc + maybe (updateTypes file msrc') return mcached' + where + getCached :: ServerMonadBase m => Path -> Maybe Text -> ClientM m (Maybe [Tools.Note Types.TypedExpr]) + getCached _ (Just _) = return Nothing + getCached file' Nothing = do + actual' <- sourceUpToDate file' + mid <- query @_ @((Bool, Int) :. ModuleId) (toQuery $ select_ ["json_extract(tags, '$.types') is 1", "mu.id"] [] [] `mappend` qModuleId `mappend` where_ ["mu.file = ?"]) + (Only file') + when (length mid > 1) $ Log.sendLog Log.Warning $ "multiple modules with same file = {}" ~~ file' + when (null mid) $ hsdevError $ NotInspected $ FileModule file' Nothing + let + [(hasTypes', mid') :. modId] = mid + if actual' && hasTypes' + then do + types' <- query @_ @(Region :. Types.TypedExpr) "select line, column, line_to, column_to, expr, type from types where module_id = ?;" (Only mid') + liftM Just $ forM types' $ \(rgn :. texpr) -> return $ Tools.Note { + Tools._noteSource = modId ^. moduleLocation, + Tools._noteRegion = rgn, + Tools._noteLevel = Nothing, + Tools._note = set Types.typedExpr Nothing texpr } + else return Nothing + + updateTypes file msrc = do + m <- setFileSourceSession ghcs' file + types' <- inSessionGhc $ do + when clear $ clearTargets + Types.fileTypes m msrc + updateProcess def [Update.setModTypes (m ^. moduleId) types'] + return $ set (each . Tools.note . Types.typedExpr) Nothing types' +runCommand (AutoFix ns) = toValue $ return $ AutoFix.corrections ns +runCommand (Refactor ns rest isPure) = toValue $ do + files <- liftM (ordNub . sort) $ mapM findPath $ mapMaybe (preview $ Tools.noteSource . moduleFile) ns + let + runFix file = do + when (not isPure) $ do + liftIO $ readFileUtf8 (view path file) >>= writeFileUtf8 (view path file) . AutoFix.refact fixRefacts' + return newCorrs' + where + findCorrs :: Path -> [Tools.Note AutoFix.Refact] -> [Tools.Note AutoFix.Refact] + findCorrs f = filter ((== Just f) . preview (Tools.noteSource . moduleFile)) + fixCorrs' = findCorrs file ns + upCorrs' = findCorrs file rest + fixRefacts' = fixCorrs' ^.. each . Tools.note + newCorrs' = AutoFix.update fixRefacts' upCorrs' + liftM concat $ mapM runFix files +runCommand (Rename nm newName fpath) = toValue $ do + m <- refineSourceModule fpath + let + mname = m ^. moduleId . moduleName + makeNote mloc r = Tools.Note { + Tools._noteSource = mloc, + Tools._noteRegion = r, + Tools._noteLevel = Nothing, + Tools._note = AutoFix.Refact "rename" (AutoFix.replace (AutoFix.fromRegion r) newName) } + + defRenames <- do + -- FIXME: Doesn't take scope into account. If you have modules with same names in different project, it will rename symbols from both + defRegions <- query @_ @Region "select n.line, n.column, n.line_to, n.column_to from names as n, modules as m where m.id == n.module_id and m.name == ? and n.name == ? and def_line is not null;" ( + mname, + nm) + return $ map (makeNote (m ^. moduleId . moduleLocation)) defRegions + + usageRenames <- do + -- FIXME: Same as above: doesn't take scope into account + usageRegions <- query @_ @(Only Path :. Region) "select m.file, n.line, n.column, n.line_to, n.column_to from names as n, modules as m where n.module_id == m.id and m.file is not null and n.resolved_module == ? and n.resolved_name == ?;" ( + mname, + nm) + return $ map (\(Only p :. r) -> makeNote (FileModule p Nothing) r) usageRegions + + return $ defRenames ++ usageRenames +runCommand (GhcEval exprs mfile) = toValue $ do + ghcw <- askSession sessionGhc + mfile' <- traverse actualFileContents mfile + case mfile' of + Nothing -> inSessionGhc ghciSession + Just (FileSource f mcts) -> do + m <- setFileSourceSession [] f + inSessionGhc $ interpretModule m mcts + async' <- liftIO $ sendTask ghcw $ do + mapM (try . evaluate) exprs + res <- waitAsync async' + return $ map toValue' res + where + waitAsync :: CommandMonad m => Async a -> m a + waitAsync a = liftIO (waitCatch a) >>= either (hsdevError . GhcError . displayException) return + toValue' :: ToJSON a => Either SomeException a -> Value + toValue' (Left (SomeException e)) = object ["fail" .= show e] + toValue' (Right s) = toJSON s +runCommand Langs = toValue $ return $ Compat.languages +runCommand Flags = toValue $ return ["-f" ++ prefix ++ f | + f <- Compat.flags, + prefix <- ["", "no-"]] +runCommand (Link hold) = toValue $ commandLink >> when hold commandHold +runCommand StopGhc = toValue $ do + inSessionGhc $ do + ms <- findSessionBy (const True) + forM_ ms $ \s -> do + Log.sendLog Log.Trace $ "stopping session: {}" ~~ s + deleteSession s +runCommand Exit = toValue serverExit + +-- TODO: Implement `targetFilter` for sql + +targetFilter :: Text -> Maybe Text -> TargetFilter -> (Text, [NamedParam]) +targetFilter mtable _ (TargetProject proj) = ( + "{t}.cabal in (select cabal from projects where name == :project or cabal == :project)" ~~ ("t" ~% mtable), + [":project" := proj]) +targetFilter mtable _ (TargetFile f) = ("{t}.file == :file" ~~ ("t" ~% mtable), [":file" := f]) +targetFilter mtable Nothing (TargetModule nm) = ("{t}.name == :module_name" ~~ ("t" ~% mtable), [":module_name" := nm]) +targetFilter mtable (Just stable) (TargetModule nm) = ( + "({t}.name == :module_name) or ({s}.id in (select e.symbol_id from exports as e, modules as em where e.module_id == em.id and em.name == :module_name))" + ~~ ("t" ~% mtable) + ~~ ("s" ~% stable), + [":module_name" := nm]) +targetFilter mtable _ (TargetPackage nm) = ("{t}.package_name == :package_name" ~~ ("t" ~% mtable), [":package_name" := nm]) +targetFilter mtable _ TargetInstalled = ("{t}.package_name is not null" ~~ ("t" ~% mtable), []) +targetFilter mtable _ TargetSourced = ("{t}.file is not null" ~~ ("t" ~% mtable), []) +targetFilter mtable _ TargetStandalone = ("{t}.file is not null and {t}.cabal is null" ~~ ("t" ~% mtable), []) + +targetFilters :: Text -> Maybe Text -> [TargetFilter] -> ([Text], [NamedParam]) +targetFilters mtable stable = second concat . unzip . map (targetFilter mtable stable) + +likePattern :: SearchQuery -> Text +likePattern (SearchQuery input stype) = case stype of + SearchExact -> escapedInput + SearchPrefix -> escapedInput `T.append` "%" + SearchInfix -> "%" `T.append` escapedInput `T.append` "%" + SearchSuffix -> "%" `T.append` escapedInput + where + escapedInput = escapeLike input + +instance ToJSON Log.Message where + toJSON m = object [ + "time" .= Log.messageTime m, + "level" .= show (Log.messageLevel m), + "component" .= show (Log.messageComponent m), + "scope" .= show (Log.messageScope m), + "text" .= Log.messageText m] + +instance FromJSON Log.Message where + parseJSON = withObject "log-message" $ \v -> Log.Message <$> + (v .:: "time") <*> + ((v .:: "level") >>= maybe (fail "invalid level") return . readMaybe) <*> + (read <$> (v .:: "component")) <*> + (read <$> (v .:: "scope")) <*> + (v .:: "text") + +-- Helper functions + +-- | Canonicalize paths +findPath :: (CommandMonad m, Paths a) => a -> m a +findPath = paths findPath' where + findPath' :: CommandMonad m => FilePath -> m FilePath + findPath' f = do + r <- commandRoot + liftIO $ canonicalizePath (normalise $ if isRelative f then r </> f else f) + +-- | Find sandbox by path +findSandbox :: CommandMonad m => Path -> m Sandbox +findSandbox fpath = do + fpath' <- findPath fpath + sbox <- liftIO $ S.findSandbox fpath' + maybe (hsdevError $ FileNotFound fpath') return sbox + +-- | Check if source file up to date +sourceUpToDate :: CommandMonad m => Path -> m Bool +sourceUpToDate fpath = do + fpath' <- findPath fpath + insps <- query @_ @Inspection "select inspection_time, inspection_opts from modules where file = ?;" (Only fpath') + when (length insps > 1) $ Log.sendLog Log.Warning $ "multiple modules with same file = {}" ~~ fpath' + maybe + (return False) + (upToDate (FileModule fpath' Nothing) []) + (listToMaybe insps) + +-- | Get source file +-- refineSourceFile :: CommandMonad m => Path -> m Path +-- refineSourceFile fpath = do +-- fpath' <- findPath fpath +-- fs <- liftM (map fromOnly) $ query "select file from modules where file == ?;" (Only fpath') +-- case fs of +-- [] -> hsdevError (NotInspected $ FileModule fpath' Nothing) +-- (f:_) -> do +-- when (length fs > 1) $ Log.sendLog Log.Warning $ "multiple modules with same file = {}" ~~ fpath' +-- return f + +-- | Get module by source +refineSourceModule :: CommandMonad m => Path -> m Module +refineSourceModule fpath = do + fpath' <- findPath fpath + ids <- query "select id, cabal from modules where file == ?;" (Only fpath') + case ids of + [] -> hsdevError (NotInspected $ FileModule fpath' Nothing) + ((i, mcabal):_) -> do + when (length ids > 1) $ Log.sendLog Log.Warning $ "multiple modules with same file = {}" ~~ fpath' + m <- SQLite.loadModule i + case mcabal of + Nothing -> do + [insp] <- query @_ @Inspection "select inspection_time, inspection_opts from modules where id = ?;" (Only i) + fresh' <- upToDate (m ^. moduleId . moduleLocation) [] insp + if fresh' + then return m + else do + defs <- askSession sessionDefines + p' <- liftIO $ preload (m ^. moduleId . moduleName) defs [] (m ^. moduleId . moduleLocation) Nothing + return $ set moduleImports (p' ^. asModule . moduleImports) m + Just cabal' -> do + proj' <- SQLite.loadProject cabal' + return $ set (moduleId . moduleLocation . moduleProject) (Just proj') m + +-- | Get file contents +actualFileContents :: CommandMonad m => FileSource -> m FileSource +actualFileContents (FileSource fpath Nothing) = fmap (FileSource fpath) (fmap (fmap snd) $ getFileContents fpath) +actualFileContents fcts = return fcts + +-- | Set session by source +setFileSourceSession :: CommandMonad m => [String] -> Path -> m Module +setFileSourceSession opts fpath = do + m <- refineSourceModule fpath + inSessionGhc $ targetSession opts m + return m + +-- | Ensure package exists +-- refinePackage :: CommandMonad m => Text -> m Text +-- refinePackage pkg = do +-- [(Only exists)] <- query "select count(*) > 0 from package_dbs where package_name == ?;" (Only pkg) +-- when (not exists) $ hsdevError (PackageNotFound pkg) +-- return pkg + +-- | Get list of enumerated sandboxes +getSandboxes :: CommandMonad m => [Path] -> m [Sandbox] +getSandboxes = traverse (findPath >=> findSandbox) + +-- | Find project by name or path +findProject :: CommandMonad m => Text -> m Project +findProject proj = do + proj' <- liftM addCabal $ findPath proj + ps <- liftM (map fromOnly) $ query "select cabal from projects where (cabal == ?) or (name == ?);" (view path proj', proj) + case ps of + [] -> hsdevError $ ProjectNotFound proj + _ -> SQLite.loadProject (head ps) + where + addCabal p + | takeExtension (view path p) == ".cabal" = p + | otherwise = over path (\p' -> p' </> (takeBaseName p' <.> "cabal")) p + +-- | Run DB update action +updateProcess :: ServerMonadBase m => Update.UpdateOptions -> [Update.UpdateM IO ()] -> ClientM m () +updateProcess uopts acts = hoist liftIO $ do + copts <- getOptions + inSessionUpdater $ hoist (flip runReaderT copts) $ runClientM $ mapM_ (Update.runUpdate uopts . runAct) acts + where + runAct act = catch act onError + onError e = Log.sendLog Log.Error $ "{}" ~~ (e :: HsDevError)
− src/HsDev/Commands.hs
@@ -1,193 +0,0 @@-{-# LANGUAGE RankNTypes, FlexibleContexts #-}--module HsDev.Commands (- -- * Commands- findDeclaration, findModule,- fileModule,- lookupSymbol,- whois,- scopeModules, scope,- completions,- moduleCompletions,-- -- * Filters- checkModule, checkDeclaration, restrictPackageDb, restrictPackageDbStack, visibleFrom,- splitIdentifier,-- -- * Helpers- fileCtx, fileCtxMaybe,-- -- * Reexports- module HsDev.Database,- module HsDev.Symbols.Types,- module Control.Monad.Except- ) where--import Control.Applicative-import Control.Lens (view, set, each, toListOf)-import Control.Monad.Except-import Data.List (delete)-import Data.Maybe-import Data.String (fromString)-import qualified Data.Text as T (isPrefixOf, split, unpack)-import System.Directory (canonicalizePath)--import System.Directory.Paths--import HsDev.Database-import HsDev.Project-import HsDev.Symbols-import HsDev.Symbols.Resolve-import HsDev.Symbols.Types-import HsDev.Symbols.Util-import HsDev.Tools.Base (matchRx, at_)-import HsDev.Util (liftE, ordNub)---- | Find declaration by name-findDeclaration :: Database -> String -> ExceptT String IO [ModuleDeclaration]-findDeclaration db ident = return $ selectDeclarations checkName db where- checkName :: ModuleDeclaration -> Bool- checkName m =- (view (moduleDeclaration . declarationName) m == fromString iname) &&- (maybe True ((view (declarationModuleId . moduleIdName) m ==) . fromString) qname)-- (qname, iname) = splitIdentifier ident---- | Find module by name-findModule :: Database -> String -> ExceptT String IO [Module]-findModule db mname = return $ selectModules ((== fromString mname) . view moduleName) db---- | Find module in file-fileModule :: Database -> FilePath -> ExceptT String IO Module-fileModule db src = do- src' <- liftE $ canonicalizePath src- maybe (throwError $ "File '" ++ src' ++ "' not found") return $ lookupFile src' db---- | Find project of module-getProject :: Database -> Project -> ExceptT String IO Project-getProject db p = do- p' <- liftE $ canonicalize p- maybe (throwError $ "Project " ++ view projectCabal p' ++ " not found") return $- refineProject db p'---- | Lookup visible within project/cabal symbol-lookupSymbol :: Database -> FilePath -> String -> ExceptT String IO [ModuleDeclaration]-lookupSymbol db file ident = do- (_, mthis, mproj) <- fileCtx db file- liftM- (filter $ checkModule $ allOf [- visibleFrom mproj mthis,- maybe (const True) inModule qname])- (newestPackage <$> findDeclaration db iname)- where- (qname, iname) = splitIdentifier ident---- | Whois symbol in scope-whois :: Database -> FilePath -> String -> ExceptT String IO [Declaration]-whois db file ident = do- (_, mthis, mproj) <- fileCtx db file- return $- newestPackage $ filter checkDecl $- view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $- moduleLocals mthis- where- (qname, iname) = splitIdentifier ident- checkDecl d = fmap fromString qname `elem` scopes d && view declarationName d == fromString iname---- | Accessible modules-scopeModules :: Database -> FilePath -> ExceptT String IO [Module]-scopeModules db file = do- (file', mthis, mproj) <- fileCtxMaybe db file- newestPackage <$> case mproj of- Nothing -> return $ maybe id (:) mthis $ selectModules (installed . view moduleId) db- Just proj -> let deps' = deps file' proj in- return $ concatMap (\p -> selectModules (p . view moduleId) db) [- inProject proj,- \m -> any (`inPackage` m) deps']- where- deps f p = delete (view projectName p) $ concatMap (view infoDepends) $ fileTargets p f---- | Symbols in scope-scope :: Database -> FilePath -> Bool -> ExceptT String IO [Declaration]-scope db file False = do- (_, mthis, mproj) <- fileCtx db file- return $ view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) mthis-scope db file True = concatMap (view moduleDeclarations) <$> scopeModules db file---- | Completions-completions :: Database -> FilePath -> String -> Bool -> ExceptT String IO [Declaration]-completions db file prefix wide = do- (_, mthis, mproj) <- fileCtx db file- return $- toListOf (each . minimalDecl) $ newestPackage $ filter checkDecl $- view moduleDeclarations $ scopeModule $ resolveOne (fileDeps file mproj db) $- dropImportLists mthis- where- (qname, iname) = splitIdentifier prefix- checkDecl d = fmap fromString qname `elem` scopes d && fromString iname `T.isPrefixOf` view declarationName d- dropImportLists m- | wide = set (moduleImports . each . importList) Nothing m- | otherwise = m---- | Module completions-moduleCompletions :: Database -> [Module] -> String -> ExceptT String IO [String]-moduleCompletions _ ms prefix = return $ map T.unpack $ ordNub $ completions' $ map (view moduleName) ms where- completions' = mapMaybe getNext where- getNext m- | fromString prefix `T.isPrefixOf` m = listToMaybe $ map snd $ dropWhile (uncurry (==)) $ zip (T.split (== '.') $ fromString prefix) (T.split (== '.') m)- | otherwise = Nothing---- | Check module-checkModule :: (ModuleId -> Bool) -> (ModuleDeclaration -> Bool)-checkModule = (. view declarationModuleId)---- | Check declaration-checkDeclaration :: (Declaration -> Bool) -> (ModuleDeclaration -> Bool)-checkDeclaration = (. view moduleDeclaration)---- | Allow only selected cabal sandbox-restrictPackageDb :: PackageDb -> ModuleId -> Bool-restrictPackageDb pdb m = inPackageDb pdb m || not (installed m)---- | Allow only selected cabal sandboxes-restrictPackageDbStack :: PackageDbStack -> ModuleId -> Bool-restrictPackageDbStack pdbs m = any (`inPackageDb` m) (packageDbs pdbs) || not (installed m)---- | Check whether module is visible from source file-visibleFrom :: Maybe Project -> Module -> ModuleId -> Bool-visibleFrom (Just p) this m = visible p (view moduleId this) m-visibleFrom Nothing this m = view moduleId this == m || installed m---- | Split identifier into module name and identifier itself-splitIdentifier :: String -> (Maybe String, String)-splitIdentifier name = fromMaybe (Nothing, name) $ do- groups <- matchRx "(([A-Z][\\w']*\\.)*)(.*)" name- return (dropDot <$> groups 1, groups `at_` 3)- where- dropDot :: String -> String- dropDot "" = ""- dropDot s = init s---- | Get context file and project-fileCtx :: Database -> FilePath -> ExceptT String IO (FilePath, Module, Maybe Project)-fileCtx db file = do- file' <- liftE $ canonicalizePath file- mthis <- fileModule db file'- mproj <- traverse (getProject db) $ projectOf $ view moduleId mthis- return (file', mthis, mproj)---- | Try get context file-fileCtxMaybe :: Database -> FilePath -> ExceptT String IO (FilePath, Maybe Module, Maybe Project)-fileCtxMaybe db file = ((\(f, m, p) -> (f, Just m, p)) <$> fileCtx db file) <|> onlyProj where- onlyProj = do- file' <- liftE $ canonicalizePath file- mproj <- liftE $ locateProject file'- mproj' <- traverse (getProject db) mproj- return (file', Nothing, mproj')---- | Restrict only modules file depends on-fileDeps :: FilePath -> Maybe Project -> Database -> Database-fileDeps file mproj = filterDB fileDeps' (const True) where- fileDeps' = liftM2 (||)- (maybe (const True) inProject mproj)- (\m -> any (`inDepsOfTarget` m) (fromMaybe [] $ fileTargets <$> mproj <*> pure file))
− src/HsDev/Database.hs
@@ -1,253 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}--module HsDev.Database (- Database(..),- databaseIntersection, nullDatabase, databaseLocals, databasePackageDbs, allModules, allDeclarations, allPackages,- fromModule, fromProject,- filterDB,- projectDB, packageDbDB, packageDbStackDB, standaloneDB,- selectModules, selectDeclarations, lookupModule, lookupInspected, lookupFile, refineProject,- getInspected,-- append, remove,-- Structured(..),- structured, structurize, merge,-- Map- ) where--import Control.Lens (set, view, preview, _Just, _Right, each, (^..))-import Control.Monad (msum, join)-import Control.DeepSeq (NFData(..))-import Data.Aeson-import Data.Either (rights)-import Data.Foldable (find)-import Data.Function (on)-import Data.Group (Group(..))-import Data.Map (Map)-import Data.Maybe-import qualified Data.Map as M--import HsDev.Symbols-import HsDev.Symbols.Util-import HsDev.Util ((.::), ordNub, mapBy)---- | HsDev database-data Database = Database {- databaseModules :: [InspectedModule],- databaseProjects :: [Project] }- deriving (Eq, Ord)--instance NFData Database where- rnf (Database ms ps) = rnf ms `seq` rnf ps--instance Group Database where- add old new = Database {- databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules new) `M.union` mapBy (view inspectedId) (databaseModules old),- databaseProjects = M.elems $ M.unionWith mergeProject- (mapBy (view projectCabal) (databaseProjects new))- (mapBy (view projectCabal) (databaseProjects old)) }- where- mergeProject pl pr = set projectDescription (msum [view projectDescription pl, view projectDescription pr]) pl- sub old new = Database {- databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules old) `M.difference` mapBy (view inspectedId) (databaseModules new),- databaseProjects = M.elems $ mapBy (view projectCabal) (databaseProjects old) `M.difference` mapBy (view projectCabal) (databaseProjects new) }- zero = Database [] []--instance Monoid Database where- mempty = zero- mappend = add--instance ToJSON Database where- toJSON (Database ms ps) = object [- "modules" .= ms,- "projects" .= ps]--instance FromJSON Database where- parseJSON = withObject "database" $ \v -> Database <$>- (v .:: "modules") <*>- (v .:: "projects")---- | Database intersection, prefers first database data-databaseIntersection :: Database -> Database -> Database-databaseIntersection l r = mempty {- databaseModules = M.elems $ mapBy (view inspectedId) (databaseModules l) `M.intersection` mapBy (view inspectedId) (databaseModules r),- databaseProjects = M.elems $ mapBy (view projectCabal) (databaseProjects l) `M.intersection` mapBy (view projectCabal) (databaseProjects r) }---- | Check if database is empty-nullDatabase :: Database -> Bool-nullDatabase db = null (databaseModules db) && null (databaseProjects db)---- | Bring all locals to scope-databaseLocals :: Database -> Database-databaseLocals db = db {- databaseModules = fmap (fmap moduleLocals) (databaseModules db) }---- | All scanned sandboxes-databasePackageDbs :: Database -> [PackageDb]-databasePackageDbs db = ordNub $ databaseModules db ^.. each . inspectionResult . _Right . moduleLocation . modulePackageDb---- | All modules-allModules :: Database -> [Module]-allModules = rights . fmap (view inspectionResult) . databaseModules---- | All declarations-allDeclarations :: Database -> [ModuleDeclaration]-allDeclarations db = do- m <- allModules db- moduleModuleDeclarations m---- | All packages-allPackages :: Database -> [ModulePackage]-allPackages = ordNub . mapMaybe (preview (moduleLocation . modulePackage . _Just)) . allModules---- | Make database from module-fromModule :: InspectedModule -> Database-fromModule m = zero {- databaseModules = [m] }---- | Make database from project-fromProject :: Project -> Database-fromProject p = zero {- databaseProjects = [p] }---- | Filter database by predicate-filterDB :: (ModuleId -> Bool) -> (Project -> Bool) -> Database -> Database-filterDB m p db = mempty {- databaseModules = filter (either (const False) (m . view moduleId) . view inspectionResult) (databaseModules db),- databaseProjects = filter p (databaseProjects db) }---- | Project database-projectDB :: Project -> Database -> Database-projectDB proj = filterDB (inProject proj) (((==) `on` view projectCabal) proj)---- | Package-db database-packageDbDB :: PackageDb -> Database -> Database-packageDbDB pdb = filterDB (inPackageDb pdb) (const False)--packageDbStackDB :: PackageDbStack -> Database -> Database-packageDbStackDB pdbs = filterDB (inPackageDbStack pdbs) (const False)---- | Standalone database-standaloneDB :: Database -> Database-standaloneDB = filterDB check' (const False) where- check' m = standalone m && byFile m---- | Select module by predicate-selectModules :: (Module -> Bool) -> Database -> [Module]-selectModules p = filter p . allModules---- | Select declaration by predicate-selectDeclarations :: (ModuleDeclaration -> Bool) -> Database -> [ModuleDeclaration]-selectDeclarations p = filter p . allDeclarations---- | Lookup module by its location and name-lookupModule :: ModuleLocation -> Database -> Maybe Module-lookupModule mloc db = do- m <- find ((== mloc) . view inspectedId) $ databaseModules db- either (const Nothing) Just $ view inspectionResult m---- | Lookup inspected module-lookupInspected :: ModuleLocation -> Database -> Maybe InspectedModule-lookupInspected mloc db = find ((== mloc) . view inspectedId) $ databaseModules db---- | Lookup module by its source file-lookupFile :: FilePath -> Database -> Maybe Module-lookupFile f = listToMaybe . selectModules (inFile f . view moduleId)---- | Refine project-refineProject :: Database -> Project -> Maybe Project-refineProject db proj = find ((== view projectCabal proj) . view projectCabal) $ databaseProjects db---- | Get inspected module-getInspected :: Database -> Module -> InspectedModule-getInspected db m = fromMaybe err $ find ((== view moduleLocation m) . view inspectedId) $ databaseModules db where- err = error "Impossible happened: getInspected"---- | Append database-append :: Database -> Database -> Database-append = add---- | Remove database-remove :: Database -> Database -> Database-remove = sub---- | Structured database-data Structured = Structured {- structuredPackageDbs :: Map PackageDb Database,- structuredProjects :: Map FilePath Database,- structuredFiles :: Database }- deriving (Eq, Ord)--instance NFData Structured where- rnf (Structured cs ps fs) = rnf cs `seq` rnf ps `seq` rnf fs--instance Group Structured where- add old new = Structured {- structuredPackageDbs = structuredPackageDbs new `M.union` structuredPackageDbs old,- structuredProjects = structuredProjects new `M.union` structuredProjects old,- structuredFiles = structuredFiles old `add` structuredFiles new }- sub old new = Structured {- structuredPackageDbs = structuredPackageDbs old `M.difference` structuredPackageDbs new,- structuredProjects = structuredProjects old `M.difference` structuredProjects new,- structuredFiles = structuredFiles old `sub` structuredFiles new }- zero = Structured zero zero zero--instance Monoid Structured where- mempty = zero- mappend = add--instance ToJSON Structured where- toJSON (Structured cs ps fs) = object [- "cabals" .= M.elems cs,- "projects" .= M.elems ps,- "files" .= fs]--instance FromJSON Structured where- parseJSON = withObject "structured" $ \v -> join $- either fail return <$> (structured <$>- (v .:: "cabals") <*>- (v .:: "projects") <*>- (v .:: "files"))--structured :: [Database] -> [Database] -> Database -> Either String Structured-structured cs ps fs = Structured <$> mkMap keyCabal cs <*> mkMap keyProj ps <*> pure fs where- mkMap :: Ord a => (Database -> Either String a) -> [Database] -> Either String (Map a Database)- mkMap key dbs = do- keys <- mapM key dbs- return $ M.fromList $ zip keys dbs- keyCabal :: Database -> Either String PackageDb- keyCabal db = unique- "No cabal"- "Different module cabals"- (ordNub <$> mapM getPackageDb (allModules db))- where- getPackageDb m = case view moduleLocation m of- InstalledModule c _ _ -> Right c- _ -> Left "Module have no cabal"- keyProj :: Database -> Either String FilePath- keyProj db = unique- "No project"- "Different module projects"- (return (databaseProjects db ^.. each . projectCabal))- -- Check that list results in one element- unique :: String -> String -> Either String [a] -> Either String a- unique _ _ (Left e) = Left e- unique no _ (Right []) = Left no- unique _ _ (Right [x]) = Right x- unique _ much (Right _) = Left much--structurize :: Database -> Structured-structurize db = Structured cs ps fs where- cs = M.fromList [(c, packageDbDB c db) | c <- ordNub (mapMaybe modPackageDb (allModules db))]- ps = M.fromList [(pname, projectDB (project pname) db) | pname <- databaseProjects db ^.. each . projectCabal]- fs = standaloneDB db--merge :: Structured -> Database-merge (Structured cs ps fs) = mconcat $ M.elems cs ++ M.elems ps ++ [fs]--modPackageDb :: Module -> Maybe PackageDb-modPackageDb m = case view moduleLocation m of- InstalledModule c _ _ -> Just c- _ -> Nothing
− src/HsDev/Database/Async.hs
@@ -1,30 +0,0 @@-module HsDev.Database.Async ( - update, clear, wait, - module Data.Async - ) where - -import Control.Concurrent.MVar -import Control.Monad.IO.Class (MonadIO(..)) -import Control.DeepSeq (force) - -import Data.Async - -import HsDev.Database (Database) - -update :: MonadIO m => Async Database -> m Database -> m () -update db act = do - db' <- act - force db' `seq` liftIO (modifyAsync db (Append db')) - -clear :: MonadIO m => Async Database -> m Database -> m () -clear db act = do - db' <- act - force db' `seq` liftIO (modifyAsync db (Remove db')) - --- | This function is used to ensure that all previous updates were applied -wait :: MonadIO m => Async Database -> m () -wait db = liftIO $ do - waitVar <- newEmptyMVar - modifyAsync db (Action $ \d -> putMVar waitVar () >> return d) - -- FIXME: what if async process is dead? - takeMVar waitVar
+ src/HsDev/Database/SQLite.hs view
@@ -0,0 +1,527 @@+{-# LANGUAGE OverloadedStrings, TypeOperators, TypeApplications #-} + +module HsDev.Database.SQLite ( + initialize, purge, + privateMemory, sharedMemory, + query, query_, queryNamed, execute, execute_, executeMany, executeNamed, + withTemporaryTable, + updatePackageDb, removePackageDb, insertPackageDb, + updateProject, removeProject, insertProject, insertBuildInfo, + updateModule, + removeModuleContents, removeModule, + insertModuleSymbols, + upsertModule, + lookupModuleLocation, lookupModule, + lookupSymbol, + lastRow, + + loadModule, loadModules, + loadProject, + + -- * Utils + escapeLike, + + -- * Reexports + module Database.SQLite.Simple, + module HsDev.Database.SQLite.Select, + module HsDev.Database.SQLite.Instances, + module HsDev.Database.SQLite.Transaction + ) where + +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.Monad.Catch +import Control.Monad.IO.Class +import Data.Aeson hiding (Error) +import Data.Generics.Uniplate.Operations +import Data.List (intercalate) +import Data.Maybe +import Data.String +import qualified Data.Set as S +import Data.Text (Text) +import qualified Data.Text as T +import Database.SQLite.Simple hiding (query, query_, queryNamed, execute, execute_, executeNamed, executeMany, withTransaction) +import qualified Database.SQLite.Simple as SQL (query, query_, queryNamed, execute, execute_, executeNamed, executeMany, withTransaction) +import Distribution.Text (display) +import Language.Haskell.Exts.Syntax hiding (Name, Module) +import Language.Haskell.Extension () +import System.Log.Simple +import Text.Format + +import System.Directory.Paths + +import HsDev.Database.SQLite.Instances +import HsDev.Database.SQLite.Schema +import HsDev.Database.SQLite.Select +import HsDev.Database.SQLite.Transaction +import qualified HsDev.Display as Display +import HsDev.Error +import HsDev.PackageDb.Types +import HsDev.Project.Types +import HsDev.Symbols.Name +import HsDev.Symbols.Parsed +import HsDev.Symbols.Types hiding (loadProject) +import qualified HsDev.Symbols.Parsed as P +import qualified HsDev.Symbols.Name as Name +import HsDev.Server.Types +import HsDev.Util + +-- | Initialize database +initialize :: String -> IO Connection +initialize p = do + conn <- open p + SQL.execute_ conn "pragma case_sensitive_like = true;" + [Only hasTables] <- SQL.query_ conn "select count(*) > 0 from sqlite_master where type == 'table';" + goodVersion <- if hasTables + then do + [Only equalVersion] <- SQL.query conn "select sum(json(value) == json(?)) > 0 from hsdev where option == 'version';" (Only $ toJSON version) + return equalVersion + else return True + unless goodVersion $ + -- TODO: Completely drop schema to reinitialize + hsdevError $ OtherError "Not implemented: dropping schema of db" + when (not hasTables || not goodVersion) $ SQL.withTransaction conn $ do + mapM_ (SQL.execute_ conn) commands + SQL.execute @(Text, Value) conn "insert into hsdev values (?, ?);" ("version", toJSON version) + return conn + +purge :: SessionMonad m => m () +purge = do + tables <- query_ @(Only String) "select name from sqlite_master where type == 'table';" + forM_ tables $ \(Only table) -> + execute_ $ fromString $ "delete from {};" ~~ table + +-- | Private memory for db +privateMemory :: String +privateMemory = ":memory:" + +-- | Shared db in memory +sharedMemory :: String +sharedMemory = "file::memory:?cache=shared" + +-- | Retries for simple queries +retried :: (MonadIO m, MonadCatch m) => m a -> m a +retried = retry def + +query :: (ToRow q, FromRow r, SessionMonad m) => Query -> q -> m [r] +query q' params = retried $ do + conn <- serverSqlDatabase + liftIO $ SQL.query conn q' params + +query_ :: (FromRow r, SessionMonad m) => Query -> m [r] +query_ q' = retried $ do + conn <- serverSqlDatabase + liftIO $ SQL.query_ conn q' + +queryNamed :: (FromRow r, SessionMonad m) => Query -> [NamedParam] -> m [r] +queryNamed q' ps' = retried $ do + conn <- serverSqlDatabase + liftIO $ SQL.queryNamed conn q' ps' + +execute :: (ToRow q, SessionMonad m) => Query -> q -> m () +execute q' params = retried $ do + conn <- serverSqlDatabase + liftIO $ SQL.execute conn q' params + +execute_ :: SessionMonad m => Query -> m () +execute_ q' = retried $ do + conn <- serverSqlDatabase + liftIO $ SQL.execute_ conn q' + +executeMany :: (ToRow q, SessionMonad m) => Query -> [q] -> m () +executeMany q' params = do + conn <- serverSqlDatabase + liftIO $ SQL.executeMany conn q' params + +executeNamed :: SessionMonad m => Query -> [NamedParam] -> m () +executeNamed q' ps' = do + conn <- serverSqlDatabase + liftIO $ SQL.executeNamed conn q' ps' + +withTemporaryTable :: SessionMonad m => String -> [String] -> m a -> m a +withTemporaryTable tableName columns = bracket_ createTable dropTable where + createTable = execute_ $ fromString $ "create temporary table {} ({});" ~~ tableName ~~ (intercalate ", " columns) + dropTable = execute_ $ fromString $ "drop table {};" ~~ tableName + +updatePackageDb :: SessionMonad m => PackageDb -> [ModulePackage] -> m () +updatePackageDb pdb pkgs = scope "update-package-db" $ do + sendLog Trace $ "update package-db: {}" ~~ Display.display pdb + removePackageDb pdb + insertPackageDb pdb pkgs + +removePackageDb :: SessionMonad m => PackageDb -> m () +removePackageDb pdb = scope "remove-package-db" $ + execute "delete from package_dbs where package_db == ?;" (Only pdb) + +insertPackageDb :: SessionMonad m => PackageDb -> [ModulePackage] -> m () +insertPackageDb pdb pkgs = scope "insert-package-db" $ forM_ pkgs $ \pkg -> + execute + "insert into package_dbs (package_db, package_name, package_version) values (?, ?, ?);" + (pdb, pkg ^. packageName, pkg ^. packageVersion) + +updateProject :: SessionMonad m => Project -> Maybe PackageDbStack -> m () +updateProject proj pdbs = scope "update-project" $ do + sendLog Trace $ "update project: {}" ~~ Display.display proj + removeProject proj + insertProject proj pdbs + +removeProject :: SessionMonad m => Project -> m () +removeProject proj = scope "remove-project" $ do + projId <- query @_ @(Only Int) "select id from projects where cabal == ?;" (Only $ proj ^. projectCabal) + case projId of + [] -> return () + pids -> do + when (length pids > 1) $ + sendLog Warning $ "multiple projects for cabal {} found" ~~ (proj ^. projectCabal) + forM_ pids $ \pid -> do + bids <- query @_ @(Only Int) "select build_info_id from targets where project_id == ?;" pid + execute "delete from projects where id == ?;" pid + execute "delete from libraries where project_id == ?;" pid + execute "delete from executables where project_id == ?;" pid + execute "delete from tests where project_id == ?;" pid + forM_ bids $ \bid -> execute "delete from build_infos where id == ?;" bid + +insertProject :: SessionMonad m => Project -> Maybe PackageDbStack -> m () +insertProject proj pdbs = scope "insert-project" $ do + execute "insert into projects (name, cabal, version, package_db_stack) values (?, ?, ?, ?);" ( + proj ^. projectName, + proj ^. projectCabal . path, + proj ^? projectDescription . _Just . projectVersion, + fmap (encode . packageDbs) pdbs) + projId <- lastRow + + forM_ (proj ^? projectDescription . _Just . projectLibrary . _Just) $ \lib -> do + buildInfoId <- insertBuildInfo $ lib ^. libraryBuildInfo + execute "insert into libraries (project_id, modules, build_info_id) values (?, ?, ?);" + (projId, encode $ lib ^. libraryModules, buildInfoId) + + forM_ (proj ^.. projectDescription . _Just . projectExecutables . each) $ \exe -> do + buildInfoId <- insertBuildInfo $ exe ^. executableBuildInfo + execute "insert into executables (project_id, name, path, build_info_id) values (?, ?, ?, ?);" + (projId, exe ^. executableName, exe ^. executablePath . path, buildInfoId) + + forM_ (proj ^.. projectDescription . _Just . projectTests . each) $ \test -> do + buildInfoId <- insertBuildInfo $ test ^. testBuildInfo + execute "insert into tests (project_id, name, enabled, main, build_info_id) values (?, ?, ?, ?, ?);" + (projId, test ^. testName, test ^. testEnabled, test ^? testMain . _Just . path, buildInfoId) + +insertBuildInfo :: SessionMonad m => Info -> m Int +insertBuildInfo info = scope "insert-build-info" $ do + execute "insert into build_infos (depends, language, extensions, ghc_options, source_dirs, other_modules) values (?, ?, ?, ?, ?, ?);" ( + encode $ info ^. infoDepends, + fmap display $ info ^. infoLanguage, + encode $ map display $ info ^. infoExtensions, + encode $ info ^. infoGHCOptions, + encode $ info ^.. infoSourceDirs . each . path, + encode $ info ^. infoOtherModules) + lastRow + +updateModule :: SessionMonad m => InspectedModule -> m () +updateModule im = scope "update-module" $ do + _ <- upsertModule im + insertModuleSymbols im + +removeModuleContents :: SessionMonad m => Int -> m () +removeModuleContents mid = do + execute "delete from imports where module_id == ?;" (Only mid) + execute "delete from exports where module_id == ?;" (Only mid) + execute "delete from scopes where module_id == ?;" (Only mid) + execute "delete from names where module_id == ?;" (Only mid) + execute "delete from types where module_id == ?;" (Only mid) + +removeModule :: SessionMonad m => Int -> m () +removeModule mid = scope "remove-module" $ do + removeModuleContents mid + execute "delete from modules where id == ?;" (Only mid) + +upsertModule :: SessionMonad m => InspectedModule -> m Int +upsertModule im = scope "upsert-module" $ do + mmid <- lookupModuleLocation (im ^. inspectedKey) + case mmid of + Nothing -> do + execute "insert into modules (file, cabal, install_dirs, package_name, package_version, installed_name, other_location, name, docs, fixities, tags, inspection_error, inspection_time, inspection_opts) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" + moduleData + lastRow + Just mid' -> do + execute "update modules set file = ?, cabal = ?, install_dirs = ?, package_name = ?, package_version = ?, installed_name = ?, other_location = ?, name = ?, docs = ?, fixities = ?, tags = ?, inspection_error = ?, inspection_time = ?, inspection_opts = ? where id == ?;" + (moduleData :. Only mid') + return mid' + where + moduleData = ( + im ^? inspectedKey . moduleFile . path, + im ^? inspectedKey . moduleProject . _Just . projectCabal, + fmap (encode . map (view path)) (im ^? inspectedKey . moduleInstallDirs), + im ^? inspectedKey . modulePackage . packageName, + im ^? inspectedKey . modulePackage . packageVersion, + im ^? inspectedKey . installedModuleName, + im ^? inspectedKey . otherLocationName) + :. ( + msum [im ^? inspected . moduleId . moduleName, im ^? inspectedKey . installedModuleName], + im ^? inspected . moduleDocs, + fmap encode $ im ^? inspected . moduleFixities, + encode $ asDict $ im ^. inspectionTags, + fmap show $ im ^? inspectionResult . _Left) + :. + fromMaybe InspectionNone (im ^? inspection) + asDict tags = object [fromString (Display.display t) .= True | t <- S.toList tags] + +insertModuleSymbols :: SessionMonad m => InspectedModule -> m () +insertModuleSymbols im = scope "insert-module-symbols" $ do + [Only mid] <- queryNamed "select id from modules where ((file is null and :file is null) or file = :file) and ((package_name is null and :package_name is null) or package_name = :package_name) and ((package_version is null and :package_version is null) or package_version = :package_version) and ((other_location is null and :other_location is null) or other_location = :other_location) and ((installed_name is null and :installed_name is null) or installed_name = :installed_name);" [ + ":file" := im ^? inspectedKey . moduleFile . path, + ":package_name" := im ^? inspectedKey . modulePackage . packageName, + ":package_version" := im ^? inspectedKey . modulePackage . packageVersion, + ":other_location" := im ^? inspectedKey . otherLocationName, + ":installed_name" := im ^? inspectedKey . installedModuleName] + -- TODO: Delete obsolete symbols (note, that they can be referenced from another modules) + removeModuleContents mid + insertModuleImports mid + insertExportSymbols mid (im ^.. inspected . moduleExports . each) + insertScopeSymbols mid (im ^.. inspected . scopeSymbols) + maybe (return ()) (insertResolvedNames mid) (im ^? inspected . moduleSource . _Just) + where + insertModuleImports :: SessionMonad m => Int -> m () + insertModuleImports mid = scope "insert-module-imports" $ do + case im ^? inspected . moduleSource . _Just of + Nothing -> return () + Just psrc -> do + let + imps = childrenBi psrc :: [ImportDecl Ann] + importRow idecl@(ImportDecl _ mname qual _ _ _ alias specList) = ( + mid, + idecl ^. pos . positionLine, + getModuleName mname, + qual, + fmap getModuleName alias, + maybe False getHiding specList, + fmap makeImportList specList) + executeMany "insert into imports (module_id, line, module_name, qualified, alias, hiding, import_list) values (?, ?, ?, ?, ?, ?, ?);" + (map importRow imps) + where + getModuleName (ModuleName _ s) = s + getHiding (ImportSpecList _ h _) = h + + makeImportList (ImportSpecList _ _ specs) = encode $ map asJson specs + asJson (IVar _ nm) = object ["name" .= fromName_ (void nm), "what" .= str' "var"] + asJson (IAbs _ ns nm) = object ["name" .= fromName_ (void nm), "what" .= str' "abs", "ns" .= fromNamespace ns] where + fromNamespace :: Namespace l -> Maybe String + fromNamespace (NoNamespace _) = Nothing + fromNamespace (TypeNamespace _) = Just "type" + fromNamespace (PatternNamespace _) = Just "pat" + asJson (IThingAll _ nm) = object ["name" .= fromName_ (void nm), "what" .= str' "all"] + asJson (IThingWith _ nm cs) = object ["name" .= fromName_ (void nm), "what" .= str' "with", "list" .= map (fromName_ . void . toName') cs] where + toName' (VarName _ n') = n' + toName' (ConName _ n') = n' + + str' :: String -> String + str' = id + + insertExportSymbols :: SessionMonad m => Int -> [Symbol] -> m () + insertExportSymbols _ [] = return () + insertExportSymbols mid syms = scope "insert-export-symbols" $ withTemporaryTable "export_symbols" (symbolsColumns ++ idColumns) $ do + dumpSymbols "export_symbols" symbolsColumns syms + updateExistingSymbols "export_symbols" + insertMissingModules "export_symbols" + insertMissingSymbols "export_symbols" + execute "insert into exports (module_id, symbol_id) select ?, symbol_id from export_symbols;" (Only mid) + + insertScopeSymbols :: SessionMonad m => Int -> [(Symbol, [Name])] -> m () + insertScopeSymbols _ [] = return () + insertScopeSymbols mid snames = scope "insert-scope-symbols" $ withTemporaryTable "scope_symbols" (symbolsColumns ++ scopeNameColumns ++ idColumns) $ do + dumpSymbols "scope_symbols" (symbolsColumns ++ scopeNameColumns) + [(s :. (Name.nameModule nm, Name.nameIdent nm)) | (s, nms) <- snames, nm <- nms] + updateExistingSymbols "scope_symbols" + insertMissingModules "scope_symbols" + insertMissingSymbols "scope_symbols" + execute "insert into scopes (module_id, qualifier, name, symbol_id) select ?, qualifier, ident, symbol_id from scope_symbols;" (Only mid) + + insertResolvedNames :: SessionMonad m => Int -> Parsed -> m () + insertResolvedNames mid p = scope "insert-resolved-names" $ do + insertNames + replaceQNames + where + insertNames = executeMany insertQuery namesData + replaceQNames = executeMany insertQuery qnamesData + insertQuery = "insert or replace into names (module_id, qualifier, name, line, column, line_to, column_to, def_line, def_column, resolved_module, resolved_name, resolve_error) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);" + namesData = map toData $ p ^.. P.names + qnamesData = map toQData $ p ^.. P.qnames + toData name = ( + mid, + Nothing :: Maybe Text, + Name.fromName_ $ void name, + name ^. P.pos . positionLine, + name ^. P.pos . positionColumn, + name ^. P.regionL . regionTo . positionLine, + name ^. P.regionL . regionTo . positionColumn) + :. ( + name ^? P.defPos . positionLine, + name ^? P.defPos . positionColumn, + (name ^? P.resolvedName) >>= Name.nameModule, + Name.nameIdent <$> (name ^? P.resolvedName), + P.resolveError name) + toQData qname = ( + mid, + Name.nameModule $ void qname, + Name.nameIdent $ void qname, + qname ^. P.pos . positionLine, + qname ^. P.pos . positionColumn, + qname ^. P.regionL . regionTo . positionLine, + qname ^. P.regionL . regionTo . positionColumn) + :. ( + qname ^? P.defPos . positionLine, + qname ^? P.defPos . positionColumn, + (qname ^? P.resolvedName) >>= Name.nameModule, + Name.nameIdent <$> (qname ^? P.resolvedName), + P.resolveError qname) + + symbolsColumns :: [String] + symbolsColumns = [ + "name", "module_name", + "file", "cabal", "install_dirs", "package_name", "package_version", "installed_name", "other_location", + "docs", + "line", "column", + "what", "type", "parent", "constructors", "args", "context", "associate", "pat_type", "pat_constructor"] + + scopeNameColumns :: [String] + scopeNameColumns = ["qualifier", "ident"] + + idColumns :: [String] + idColumns = ["module_id", "symbol_id"] + + -- Dumps symbol data to temporary row and fills with 'module_id' and 'symbol_id' fields + dumpSymbols :: (SessionMonad m, ToRow q) => String -> [String] -> [q] -> m () + dumpSymbols tableName columnNames rows = scope "dump-symbols" $ do + executeMany (fromString ("insert into {} ({}) values ({});" ~~ tableName ~~ intercalate ", " columnNames ~~ intercalate ", " (replicate (length columnNames) "?"))) (map toRow rows) + updateModuleIds tableName + updateSymbolIds tableName + + updateModuleIds :: SessionMonad m => String -> m () + updateModuleIds tableName = + execute_ (fromString ("update {table} set module_id = (select m.id from modules as m where ((m.name is null and {table}.module_name is null) or m.name = {table}.module_name) and ((m.file is null and {table}.file is null) or m.file = {table}.file) and ((m.package_name is null and {table}.package_name is null) or m.package_name = {table}.package_name) and ((m.package_version is null and {table}.package_version is null) or m.package_version = {table}.package_version) and ((m.installed_name is null and {table}.installed_name is null) or m.installed_name = {table}.installed_name) and ((m.other_location is null and {table}.other_location is null) or m.other_location = {table}.other_location));" ~~ ("table" ~% tableName))) + + updateSymbolIds :: SessionMonad m => String -> m () + updateSymbolIds tableName = + execute_ (fromString ("update {table} set symbol_id = (select s.id from symbols as s where s.name = {table}.name and s.module_id = {table}.module_id);" ~~ ("table" ~% tableName))) + + updateExistingSymbols :: SessionMonad m => String -> m () + updateExistingSymbols tableName = scope "update-existing-symbols" $ do + execute_ (fromString ("replace into symbols (id, name, module_id, docs, line, column, what, type, parent, constructors, args, context, associate, pat_type, pat_constructor) select symbols.id, {table}.name, {table}.module_id, {table}.docs, {table}.line, {table}.column, {table}.what, {table}.type, {table}.parent, {table}.constructors, {table}.args, {table}.context, {table}.associate, {table}.pat_type, {table}.pat_constructor from {table} inner join symbols on (symbols.name == {table}.name) and (symbols.module_id == {table}.module_id);" ~~ ("table" ~% tableName))) + + insertMissingModules :: SessionMonad m => String -> m () + insertMissingModules tableName = scope "insert-missing-modules" $ do + execute_ (fromString ("insert into modules (file, cabal, install_dirs, package_name, package_version, installed_name, other_location, name) select distinct file, cabal, install_dirs, package_name, package_version, installed_name, other_location, module_name from {} where module_id is null;" ~~ tableName)) + updateModuleIds tableName + + insertMissingSymbols :: SessionMonad m => String -> m () + insertMissingSymbols tableName = scope "insert-missing-symbols" $ do + execute_ (fromString ("insert into symbols (name, module_id, docs, line, column, what, type, parent, constructors, args, context, associate, pat_type, pat_constructor) select distinct name, module_id, docs, line, column, what, type, parent, constructors, args, context, associate, pat_type, pat_constructor from {} where module_id is not null and symbol_id is null;" ~~ tableName)) + updateSymbolIds tableName + +lookupModuleLocation :: SessionMonad m => ModuleLocation -> m (Maybe Int) +lookupModuleLocation m = do + mids <- queryNamed "select id from modules where ((file is null and :file is null) or file = :file) and ((package_name is null and :package_name is null) or package_name = :package_name) and ((package_version is null and :package_version is null) or package_version = :package_version) and ((installed_name is null and :installed_name is null) or installed_name = :installed_name) and ((other_location is null and :other_location is null) or other_location = :other_location);" [ + ":file" := m ^? moduleFile . path, + ":package_name" := m ^? modulePackage . packageName, + ":package_version" := m ^? modulePackage . packageVersion, + ":installed_name" := m ^? installedModuleName, + ":other_location" := m ^? otherLocationName] + when (length mids > 1) $ sendLog Warning $ "different modules with location: {}" ~~ Display.display m + return $ listToMaybe [mid | Only mid <- mids] + +lookupModule :: SessionMonad m => ModuleId -> m (Maybe Int) +lookupModule m = do + mids <- queryNamed "select id from modules where ((name is null and :name is null) or name = :name) and ((file is null and :file is null) or file = :file) and ((package_name is null and :package_name is null) or package_name = :package_name) and ((package_version is null and :package_version is null) or package_version = :package_version) and ((installed_name is null and :installed_name is null) or installed_name = :installed_name) and ((other_location is null and :other_location is null) or other_location = :other_location);" [ + ":name" := m ^. moduleName, + ":file" := m ^? moduleLocation . moduleFile . path, + ":package_name" := m ^? moduleLocation . modulePackage . packageName, + ":package_version" := m ^? moduleLocation . modulePackage . packageVersion, + ":installed_name" := m ^? moduleLocation . installedModuleName, + ":other_location" := m ^? moduleLocation . otherLocationName] + when (length mids > 1) $ sendLog Warning $ "different modules with same name and location: {}" ~~ (m ^. moduleName) + return $ listToMaybe [mid | Only mid <- mids] + +lookupSymbol :: SessionMonad m => Int -> SymbolId -> m (Maybe Int) +lookupSymbol mid sym = do + sids <- query "select id from symbols where name == ? and module_id == ?;" ( + sym ^. symbolName, + mid) + when (length sids > 1) $ sendLog Warning $ "different symbols with same module id: {}.{}" ~~ show mid ~~ (sym ^. symbolName) + return $ listToMaybe [sid | Only sid <- sids] + +lastRow :: SessionMonad m => m Int +lastRow = do + [Only i] <- query_ "select last_insert_rowid();" + return i + +loadModule :: SessionMonad m => Int -> m Module +loadModule mid = scope "load-module" $ do + ms <- query @_ @(ModuleId :. (Maybe Text, Maybe Value, Int)) (toQuery (qModuleId `mappend` select_ ["mu.docs", "mu.fixities", "mu.id"] [] [fromString "mu.id == ?"])) (Only mid) + case ms of + [] -> sqlFailure $ "module with id {} not found" ~~ mid + mods@((mid' :. (mdocs, mfixities, _)):_) -> do + when (length mods > 1) $ sendLog Warning $ "multiple modules with same id = {} found" ~~ mid + syms <- query @_ @Symbol (toQuery (qSymbol `mappend` select_ [] ["exports as e"] ["e.module_id == ?", "e.symbol_id == s.id"])) (Only mid) + inames <- query @_ @(Only Text) "select distinct module_name from imports where module_id == ?;" (Only mid) + return $ Module { + _moduleId = mid', + _moduleDocs = mdocs, + _moduleImports = map fromOnly inames, + _moduleExports = syms, + _moduleFixities = fromMaybe [] (mfixities >>= fromJSON'), + _moduleScope = mempty, + _moduleSource = Nothing } + +loadModules :: (SessionMonad m, ToRow q) => String -> q -> m [Module] +loadModules selectExpr args = scope "load-modules" $ do + ms <- query @_ @(ModuleId :. (Maybe Text, Maybe Value, Int)) (toQuery (qModuleId `mappend` select_ ["mu.docs", "mu.fixities", "mu.id"] [] [fromString $ "mu.id in (" ++ selectExpr ++ ")"])) args + forM ms $ \(mid' :. (mdocs, mfixities, mid)) -> do + syms <- query @_ @Symbol (toQuery (qSymbol `mappend` select_ [] ["exports as e"] ["e.module_id == ?", "e.symbol_id == s.id"])) (Only mid) + inames <- query @_ @(Only Text) "select distinct module_name from imports where module_id == ?;" (Only mid) + return $ Module { + _moduleId = mid', + _moduleDocs = mdocs, + _moduleImports = map fromOnly inames, + _moduleExports = syms, + _moduleFixities = fromMaybe [] (mfixities >>= fromJSON'), + _moduleScope = mempty, + _moduleSource = Nothing } + +loadProject :: SessionMonad m => Path -> m Project +loadProject cabal = scope "load-project" $ do + projs <- query @_ @(Only Int :. Project) "select id, name, cabal, version from projects where cabal == ?;" (Only $ view path cabal) + (Only pid :. proj) <- case projs of + [] -> sqlFailure $ "project with cabal {} not found" ~~ view path cabal + _ -> do + when (length projs > 1) $ sendLog Warning $ "multiple projects with same cabal = {} found" ~~ view path cabal + return $ head projs + + libs <- query (toQuery $ select_ ["lib.modules"] ["libraries as lib"] [] `mappend` qBuildInfo `mappend` where_ [ + "lib.build_info_id == bi.id", + "lib.project_id == ?"]) + (Only pid) + + exes <- query (toQuery $ select_ ["exe.name", "exe.path"] ["executables as exe"] [] `mappend` qBuildInfo `mappend` where_ [ + "exe.build_info_id == bi.id", + "exe.project_id == ?"]) + (Only pid) + + tests <- query (toQuery $ select_ ["tst.name", "tst.enabled", "tst.main"] ["tests as tst"] [] `mappend` qBuildInfo `mappend` where_ [ + "tst.build_info_id == bi.id", + "tst.project_id == ?"]) + (Only pid) + + return $ + set (projectDescription . _Just . projectLibrary) (listToMaybe libs) . + set (projectDescription . _Just . projectExecutables) exes . + set (projectDescription . _Just . projectTests) tests $ + proj + +escapeLike :: Text -> Text +escapeLike = T.replace "\\" "\\\\" . T.replace "%" "\\%" . T.replace "_" "\\_" + +-- Util + +sqlFailure :: SessionMonad m => Text -> m a +sqlFailure msg = do + sendLog Error msg + hsdevError $ SQLiteError $ T.unpack msg
+ src/HsDev/Database/SQLite/Instances.hs view
@@ -0,0 +1,226 @@+{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Database.SQLite.Instances ( + JSON(..) + ) where + +import Control.Lens ((^.), (^?), _Just) +import Data.Aeson as A +import Data.Maybe +import Data.Foldable +import qualified Data.Text as T +import qualified Data.Text.Encoding as T +import qualified Data.ByteString.Lazy as L +import Database.SQLite.Simple +import Database.SQLite.Simple.ToField +import Database.SQLite.Simple.FromField +import Language.Haskell.Extension +import Text.Format + +import System.Directory.Paths +import HsDev.Symbols.Location +import HsDev.Symbols.Types +import HsDev.Tools.Ghc.Types +import HsDev.Util + +instance ToField Value where + toField = SQLBlob . L.toStrict . encode + +instance FromField Value where + fromField fld = case fieldData fld of + SQLText s -> either fail return . eitherDecode . L.fromStrict . T.encodeUtf8 $ s + SQLBlob s -> either fail return . eitherDecode . L.fromStrict $ s + _ -> fail "invalid json field type" + +newtype JSON a = JSON { getJSON :: a } + deriving (Eq, Ord, Read, Show) + +instance ToJSON a => ToField (JSON a) where + toField = SQLBlob . L.toStrict . encode . getJSON + +instance FromJSON a => FromField (JSON a) where + fromField fld = case fieldData fld of + SQLText s -> either fail (return . JSON) . eitherDecode . L.fromStrict . T.encodeUtf8 $ s + SQLBlob s -> either fail (return . JSON) . eitherDecode . L.fromStrict $ s + _ -> fail "invalid json field type" + +instance FromRow Position where + fromRow = Position <$> field <*> field + +instance ToRow Position where + toRow (Position l c) = [toField l, toField c] + +instance FromRow Region where + fromRow = Region <$> fromRow <*> fromRow + +instance ToRow Region where + toRow (Region f t) = toRow f ++ toRow t + +instance FromRow ModulePackage where + fromRow = ModulePackage <$> field <*> (fromMaybe T.empty <$> field) + +instance ToRow ModulePackage where + toRow (ModulePackage name ver) = [toField name, toField ver] + +instance FromRow ModuleLocation where + fromRow = do + file <- field + cabal <- field + dirs <- field + pname <- field + pver <- field + iname <- field + other <- field + + maybe (fail $ "Can't parse module location: {}" ~~ show (file, cabal, dirs, pname, pver, iname, other)) return $ msum [ + FileModule <$> file <*> pure (project <$> cabal), + InstalledModule <$> maybe (pure []) fromJSON' dirs <*> (ModulePackage <$> pname <*> pver) <*> iname, + OtherLocation <$> other, + pure NoLocation] + +instance ToRow ModuleLocation where + toRow mloc = [ + toField $ mloc ^? moduleFile, + toField $ mloc ^? moduleProject . _Just . projectCabal, + toField $ fmap toJSON $ mloc ^? moduleInstallDirs, + toField $ mloc ^? modulePackage . packageName, + toField $ mloc ^? modulePackage . packageVersion, + toField $ mloc ^? installedModuleName, + toField $ mloc ^? otherLocationName] + +instance FromRow ModuleId where + fromRow = ModuleId <$> field <*> fromRow + +instance ToRow ModuleId where + toRow mid = toField (mid ^. moduleName) : toRow (mid ^. moduleLocation) + +instance FromRow SymbolId where + fromRow = SymbolId <$> field <*> fromRow + +instance ToRow SymbolId where + toRow (SymbolId nm mid) = toField nm : toRow mid + +instance FromRow Symbol where + fromRow = Symbol <$> fromRow <*> field <*> pos <*> infoP where + pos = do + line <- field + column <- field + return $ Position <$> line <*> column + infoP = do + what <- str' <$> field + ty <- field + parent <- field + ctors <- field + args <- field + ctx <- field + assoc <- field + patTy <- field + patCtor <- field + maybe (fail $ "Can't parse symbol info: {}" ~~ show (what, ty, parent, ctors, args, ctx, assoc, patTy, patCtor)) return $ case what of + "function" -> return $ Function ty + "method" -> Method <$> pure ty <*> parent + "selector" -> Selector <$> pure ty <*> parent <*> (fromJSON' =<< ctors) + "ctor" -> Constructor <$> (fromJSON' =<< args) <*> parent + "type" -> Type <$> (fromJSON' =<< args) <*> (fromJSON' =<< ctx) + "newtype" -> NewType <$> (fromJSON' =<< args) <*> (fromJSON' =<< ctx) + "data" -> Data <$> (fromJSON' =<< args) <*> (fromJSON' =<< ctx) + "class" -> Class <$> (fromJSON' =<< args) <*> (fromJSON' =<< ctx) + "type-family" -> TypeFam <$> (fromJSON' =<< args) <*> (fromJSON' =<< ctx) <*> pure assoc + "data-family" -> DataFam <$> (fromJSON' =<< args) <*> (fromJSON' =<< ctx) <*> pure assoc + "pat-ctor" -> PatConstructor <$> (fromJSON' =<< args) <*> pure patTy + "pat-selector" -> PatSelector <$> pure ty <*> pure patTy <*> patCtor + _ -> Nothing + str' :: String -> String + str' = id + +instance ToRow Symbol where + toRow sym = concat [ + toRow (sym ^. symbolId), + [toField $ sym ^. symbolDocs], + maybe [SQLNull, SQLNull] toRow (sym ^. symbolPosition), + info] + where + info = [ + toField $ symbolType sym, + toField $ sym ^? symbolInfo . functionType . _Just, + toField $ msum [sym ^? symbolInfo . parentClass, sym ^? symbolInfo . parentType], + toField $ toJSON $ sym ^? symbolInfo . selectorConstructors, + toField $ toJSON $ sym ^? symbolInfo . typeArgs, + toField $ toJSON $ sym ^? symbolInfo . typeContext, + toField $ sym ^? symbolInfo . familyAssociate . _Just, + toField $ sym ^? symbolInfo . patternType . _Just, + toField $ sym ^? symbolInfo . patternConstructor] + +instance FromRow Project where + fromRow = do + name <- field + cabal <- field + ver <- field + return $ Project name (takeDir cabal) cabal $ Just $ ProjectDescription ver Nothing [] [] + +instance FromRow Library where + fromRow = do + mods <- field >>= maybe (fail "Error parsing library modules") return . fromJSON' + binfo <- fromRow + return $ Library mods binfo + +instance FromRow Executable where + fromRow = Executable <$> field <*> field <*> fromRow + +instance FromRow Test where + fromRow = Test <$> field <*> field <*> field <*> fromRow + +instance FromRow Info where + fromRow = Info <$> + (field >>= maybe (fail "Error parsing depends") return . fromJSON') <*> + field <*> + (field >>= maybe (fail "Error parsing extensions") return . fromJSON') <*> + (field >>= maybe (fail "Error parsing ghc-options") return . fromJSON') <*> + (field >>= maybe (fail "Error parsing source-dirs") return . fromJSON') <*> + (field >>= maybe (fail "Error parsing other-modules") return . fromJSON') + +instance FromField Language where + fromField fld = case fieldData fld of + SQLText txt -> parseDT "Language" (T.unpack txt) + _ -> fail "Can't parse language, invalid type" + +instance ToField PackageDb where + toField GlobalDb = toField ("global-db" :: String) + toField UserDb = toField ("user-db" :: String) + toField (PackageDb p) = toField ("package-db:" ++ T.unpack p) + +instance FromField PackageDb where + fromField fld = do + s <- fromField fld + case s of + "global-db" -> return GlobalDb + "user-db" -> return UserDb + _ -> case T.stripPrefix "package-db:" s of + Just p' -> return $ PackageDb p' + Nothing -> fail $ "Can't parse package-db, invalid string: " ++ T.unpack s + +instance FromRow SymbolUsage where + fromRow = SymbolUsage <$> fromRow <*> fromRow <*> fromRow + +instance FromRow Inspection where + fromRow = do + tm <- field + opts <- field + case (tm, opts) of + (Nothing, Nothing) -> return InspectionNone + (_, Just opts') -> InspectionAt (maybe 0 (fromRational . (toRational :: Double -> Rational)) tm) <$> + maybe (fail "Error parsing inspection opts") return (fromJSON' opts') + (Just _, Nothing) -> fail "Error parsing inspection data, time is set, but flags are null" + +instance ToRow Inspection where + toRow InspectionNone = [SQLNull, SQLNull] + toRow (InspectionAt tm opts) = [ + if tm == 0 then SQLNull else toField (fromRational (toRational tm) :: Double), + toField $ toJSON opts] + +instance FromRow TypedExpr where + fromRow = TypedExpr <$> field <*> field + +instance ToRow TypedExpr where + toRow (TypedExpr e t) = [toField e, toField t]
+ src/HsDev/Database/SQLite/Schema.hs view
@@ -0,0 +1,20 @@+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} + +module HsDev.Database.SQLite.Schema ( + schema, commands + ) where + +import qualified Data.Text as T +import Data.String +import Database.SQLite.Simple (Query) + +import HsDev.Database.SQLite.Schema.TH + +schema :: T.Text +schema = T.pack $schemaExp + +commands :: [Query] +commands = + map (fromString . T.unpack) $ + filter (not . T.null) $ + map T.strip $ T.splitOn ";" schema
+ src/HsDev/Database/SQLite/Schema/TH.hs view
@@ -0,0 +1,16 @@+{-# LANGUAGE TemplateHaskell #-} + +module HsDev.Database.SQLite.Schema.TH ( + schemaExp + ) where + +import System.Directory +import Language.Haskell.TH +import Language.Haskell.TH.Syntax + +schemaExp :: ExpQ +schemaExp = do + schemaFile <- runIO $ canonicalizePath "data/hsdev.sql" + addDependentFile schemaFile + s <- runIO (readFile "data/hsdev.sql") + [e| s |]
+ src/HsDev/Database/SQLite/Select.hs view
@@ -0,0 +1,111 @@+{-# LANGUAGE OverloadedStrings #-} + +module HsDev.Database.SQLite.Select ( + Select(..), select_, where_, buildQuery, toQuery, + qSymbolId, qSymbol, qModuleLocation, qModuleId, qBuildInfo + ) where + +import Data.String +import Data.Text (Text) +import qualified Data.Text as T +import Database.SQLite.Simple +import Text.Format + +data Select = Select { + selectColumns :: [Text], + selectTables :: [Text], + selectConditions :: [Text] } + deriving (Eq, Ord, Read, Show) + +instance Monoid Select where + mempty = Select mempty mempty mempty + Select lc lt lcond `mappend` Select rc rt rcond = Select + (lc `mappend` rc) + (lt `mappend` rt) + (lcond `mappend` rcond) + +select_ :: [Text] -> [Text] -> [Text] -> Select +select_ = Select + +where_ :: [Text] -> Select +where_ = select_ [] [] + +buildQuery :: Select -> String +buildQuery (Select cols tables conds) = "select {} from {} where {}" + ~~ T.intercalate ", " cols + ~~ T.intercalate ", " tables + ~~ T.intercalate " and " (map (\cond -> T.concat ["(", cond, ")"]) conds) + +toQuery :: Select -> Query +toQuery = fromString . buildQuery + +qSymbolId :: Select +qSymbolId = select_ + [ + "s.name", + "m.name", + "m.file", + "m.cabal", + "m.install_dirs", + "m.package_name", + "m.package_version", + "m.installed_name", + "m.other_location"] + ["modules as m", "symbols as s"] + ["m.id == s.module_id"] + +qSymbol :: Select +qSymbol = qSymbolId `mappend` select_ cols [] [] where + cols = [ + "s.docs", + "s.line", + "s.column", + "s.what", + "s.type", + "s.parent", + "s.constructors", + "s.args", + "s.context", + "s.associate", + "s.pat_type", + "s.pat_constructor"] + +qModuleLocation :: Select +qModuleLocation = select_ + [ + "ml.file", + "ml.cabal", + "ml.install_dirs", + "ml.package_name", + "ml.package_version", + "ml.installed_name", + "ml.other_location"] + ["modules as ml"] + [] + +qModuleId :: Select +qModuleId = select_ + [ + "mu.name", + "mu.file", + "mu.cabal", + "mu.install_dirs", + "mu.package_name", + "mu.package_version", + "mu.installed_name", + "mu.other_location"] + ["modules as mu"] + [] + +qBuildInfo :: Select +qBuildInfo = select_ + [ + "bi.depends", + "bi.language", + "bi.extensions", + "bi.ghc_options", + "bi.source_dirs", + "bi.other_modules" + ] + ["build_infos as bi"] + []
+ src/HsDev/Database/SQLite/Transaction.hs view
@@ -0,0 +1,98 @@+{-# LANGUAGE TypeApplications, MultiWayIf, OverloadedStrings #-} + +module HsDev.Database.SQLite.Transaction ( + TransactionType(..), + Retries(..), def, noRetry, retryForever, retryN, + + -- * Transactions + withTransaction, beginTransaction, commitTransaction, rollbackTransaction, + transaction, transaction_, + + -- * Retry functions + retry, retry_ + ) where + +import Control.Concurrent +import Control.Monad.Catch +import Control.Monad.IO.Class +import Data.Default +import Database.SQLite.Simple as SQL hiding (withTransaction) + +import HsDev.Server.Types (SessionMonad, serverSqlDatabase) + +-- | Three types of transactions +data TransactionType = Deferred | Immediate | Exclusive + deriving (Eq, Ord, Read, Show) + +-- | Retry config +data Retries = Retries { + retriesIntervals :: [Int], + retriesError :: SQLError -> Bool } + +instance Default Retries where + def = Retries (replicate 100 100000) $ \e -> sqlError e `elem` [ErrorBusy, ErrorLocked] + +-- | Don't retry +noRetry :: Retries +noRetry = Retries [] (const False) + +-- | Retry forever +retryForever :: Int -> Retries +retryForever interval = def { retriesIntervals = repeat interval } + +-- | Retry with interval N times +retryN :: Int -> Int -> Retries +retryN interval times = def { retriesIntervals = replicate times interval } + +-- | Run actions inside transaction +withTransaction :: (MonadIO m, MonadMask m) => Connection -> TransactionType -> Retries -> m a -> m a +withTransaction conn t rs act = mask $ \restore -> do + mretry restore (beginTransaction conn t) + (restore act <* mretry restore (commitTransaction conn)) `onException` rollbackTransaction conn + where + mretry restore' fn = mretry' (retriesIntervals rs) where + mretry' [] = fn + mretry' (tm:tms) = catch @_ @SQLError fn $ \e -> if + | retriesError rs e -> do + _ <- restore' $ liftIO $ threadDelay tm + mretry' tms + | otherwise -> throwM e + +-- | Begin transaction +beginTransaction :: MonadIO m => Connection -> TransactionType -> m () +beginTransaction conn t = liftIO $ SQL.execute_ conn $ case t of + Deferred -> "begin transaction;" + Immediate -> "begin immediate transaction;" + Exclusive -> "begin exclusive transaction;" + +-- | Commit transaction +commitTransaction :: MonadIO m => Connection -> m () +commitTransaction conn = liftIO $ SQL.execute_ conn "commit transaction;" + +-- | Rollback transaction +rollbackTransaction :: MonadIO m => Connection -> m () +rollbackTransaction conn = liftIO $ SQL.execute_ conn "rollback transaction;" + +-- | Run transaction in @SessionMonad@ +transaction :: SessionMonad m => TransactionType -> Retries -> m a -> m a +transaction t rs act = do + conn <- serverSqlDatabase + withTransaction conn t rs act + +-- | Transaction with default retries config +transaction_ :: SessionMonad m => TransactionType -> m a -> m a +transaction_ t = transaction t def + +-- | Retry operation +retry :: (MonadIO m, MonadCatch m) => Retries -> m a -> m a +retry rs = retry' (retriesIntervals rs) where + retry' [] fn = fn + retry' (tm:tms) fn = catch @_ @SQLError fn $ \e -> if + | retriesError rs e -> do + liftIO $ threadDelay tm + retry' tms fn + | otherwise -> throwM e + +-- | Retry with default params +retry_ :: (MonadIO m, MonadCatch m) => m a -> m a +retry_ = retry def
src/HsDev/Database/Update.hs view
@@ -1,447 +1,601 @@-{-# LANGUAGE FlexibleContexts, OverloadedStrings, MultiParamTypeClasses #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Database.Update (- Status(..), Progress(..), Task(..), isStatus,- UpdateOptions(..),-- UpdateM(..),- runUpdate,-- postStatus, waiter, updater, loadCache, getCache, runTask, runTasks,- readDB,-- scanModule, scanModules, scanFile, scanFileContents, scanCabal, prepareSandbox, scanSandbox, scanPackageDb, scanProjectFile, scanProjectStack, scanProject, scanDirectory, scanContents,- scanDocs, inferModTypes,- scan,- updateEvent, processEvent,-- module HsDev.Database.Update.Types,-- module HsDev.Watcher,-- module Control.Monad.Except- ) where--import Control.Applicative ((<|>))-import Control.Arrow-import Control.Concurrent.Lifted (fork)-import Control.DeepSeq-import Control.Lens (preview, _Just, view, over, set, _1, mapMOf_, each, (^..), _head, _Right)-import Control.Monad.Catch (catch)-import Control.Monad.Except-import Control.Monad.Reader-import Control.Monad.Writer-import Data.Aeson-import Data.Aeson.Types-import Data.Foldable (toList)-import qualified Data.Map as M-import Data.Maybe (mapMaybe, isJust, fromMaybe)-import qualified Data.Text as T (unpack)-import System.Directory (canonicalizePath, doesFileExist)-import System.FilePath-import qualified System.Log.Simple as Log--import HsDev.Error-import qualified HsDev.Cache.Structured as Cache-import HsDev.Database-import HsDev.Database.Async hiding (Event)-import HsDev.Display-import HsDev.Inspect (inspectDocs, inspectDocsGhc)-import HsDev.Project-import HsDev.Sandbox-import HsDev.Stack-import HsDev.Symbols-import HsDev.Tools.Ghc.Session hiding (wait)-import HsDev.Tools.Ghc.Types (inferTypes)-import HsDev.Tools.HDocs-import qualified HsDev.Scan as S-import HsDev.Scan.Browse-import HsDev.Util (isParent, ordNub)-import qualified HsDev.Util as Util (withCurrentDirectory)-import HsDev.Server.Types (commandNotify, serverWriteCache, serverReadCache)-import HsDev.Server.Message-import HsDev.Database.Update.Types-import HsDev.Watcher-import Text.Format--onStatus :: UpdateMonad m => m ()-onStatus = asks (view updateTasks) >>= commandNotify . Notification . toJSON . reverse--childTask :: UpdateMonad m => Task -> m a -> m a-childTask t = local (over updateTasks (t:))--isStatus :: Value -> Bool-isStatus = isJust . parseMaybe (parseJSON :: Value -> Parser Task)--runUpdate :: ServerMonadBase m => UpdateOptions -> UpdateM m a -> ClientM m a-runUpdate uopts act = Log.scope "update" $ do- (r, updatedMods) <- runWriterT (runUpdateM act' `runReaderT` uopts)- db <- askSession sessionDatabase- wait db- dbval <- liftIO $ readAsync db- let- dbs = ordNub $ mapMaybe (preview modulePackageDb) updatedMods- projs = ordNub $ mapMaybe (preview $ moduleProject . _Just) updatedMods- stand = any moduleStandalone updatedMods-- modifiedDb = mconcat $ concat [- map (`packageDbDB` dbval) dbs,- map (`projectDB` dbval) projs,- [standaloneDB dbval | stand]]- serverWriteCache modifiedDb- return r- where- act' = do- (r, mlocs') <- liftM (second $ filter (isJust . preview moduleFile)) $ listen act- db <- askSession sessionDatabase- wait db- let- getMods :: (MonadIO m) => m [InspectedModule]- getMods = do- db' <- liftIO $ readAsync db- return $ filter ((`elem` mlocs') . view inspectedId) $ toList $ databaseModules db'- when (view updateDocs uopts) $ do- Log.sendLog Log.Trace "forking inspecting source docs"- void $ fork (getMods >>= waiter . mapM_ scanDocs_)- when (view updateInfer uopts) $ do- Log.sendLog Log.Trace "forking inferring types"- void $ fork (getMods >>= waiter . mapM_ inferModTypes_)- return r- scanDocs_ :: UpdateMonad m => InspectedModule -> m ()- scanDocs_ im = do- im' <- (S.scanModify (\opts _ -> liftIO . inspectDocs opts) im) <|> return im- updater $ fromModule im'- inferModTypes_ :: UpdateMonad m => InspectedModule -> m ()- inferModTypes_ im = do- -- TODO: locate sandbox- s <- getSession- im' <- (S.scanModify (infer' s) im) <|> return im- updater $ fromModule im'- infer' :: UpdateMonad m => Session -> [String] -> PackageDbStack -> Module -> m Module- infer' s opts _ m = case preview (moduleLocation . moduleFile) m of- Nothing -> return m- Just _ -> liftIO $ inWorker (sessionGhc s) $ do- targetSession opts m- inferTypes opts m Nothing---- | Post status-postStatus :: UpdateMonad m => Task -> m ()-postStatus t = childTask t onStatus---- | Wait DB to complete actions-waiter :: UpdateMonad m => m () -> m ()-waiter act = do- db <- askSession sessionDatabase- act- wait db---- | Update task result to database-updater :: UpdateMonad m => Database -> m ()-updater db' = do- db <- askSession sessionDatabase- update db $ return $!! db'- tell $!! map (view moduleLocation) $ allModules db'---- | Clear obsolete data from database-cleaner :: UpdateMonad m => m Database -> m ()-cleaner act = do- db <- askSession sessionDatabase- db' <- act- clear db $ return $!! db'---- | Get data from cache without updating DB-loadCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> m Database-loadCache act = do- mdat <- serverReadCache act- return $ fromMaybe mempty mdat---- | Load data from cache if not loaded yet and wait-getCache :: UpdateMonad m => (FilePath -> ExceptT String IO Structured) -> (Database -> Database) -> m Database-getCache act check = do- dbval <- liftM check readDB- if nullDatabase dbval- then do- db <- loadCache act- waiter $ updater db- return db- else- return dbval---- | Run one task-runTask :: (Display t, UpdateMonad m, NFData a) => String -> t -> m a -> m a-runTask action subj act = Log.scope "task" $ do- postStatus $ set taskStatus StatusWorking task- x <- childTask task act- x `deepseq` postStatus (set taskStatus StatusOk task)- return x- `catch`- (\e -> postStatus (set taskStatus (StatusError e) task) >> hsdevError e)- where- task = Task {- _taskName = action,- _taskStatus = StatusWorking,- _taskSubjectType = displayType subj,- _taskSubjectName = display subj,- _taskProgress = Nothing }---- | Run many tasks with numeration-runTasks :: UpdateMonad m => [m ()] -> m ()-runTasks ts = zipWithM_ taskNum [1..] (map noErr ts) where- total = length ts- taskNum n = local setProgress where- setProgress = set (updateTasks . _head . taskProgress) (Just (Progress n total))- noErr v = v `mplus` return ()---- | Get database value-readDB :: SessionMonad m => m Database-readDB = askSession sessionDatabase >>= liftIO . readAsync---- | Scan module-scanModule :: UpdateMonad m => [String] -> ModuleLocation -> Maybe String -> m ()-scanModule opts mloc mcts = runTask "scanning" mloc $ Log.scope "module" $ do- defs <- askSession sessionDefines- im <- S.scanModule defs opts mloc mcts- updater $ fromModule im- _ <- return $ view inspectionResult im- return ()---- | Scan modules-scanModules :: UpdateMonad m => [String] -> [S.ModuleToScan] -> m ()-scanModules opts ms = runTasks $- [scanProjectFile opts p >> return () | p <- ps] ++- [scanModule (opts ++ mopts) m mcts | (m, mopts, mcts) <- ms]- where- ps = ordNub $ mapMaybe (toProj . view _1) ms- toProj (FileModule _ p) = fmap (view projectCabal) p- toProj _ = Nothing---- | Scan source file-scanFile :: UpdateMonad m => [String] -> FilePath -> m ()-scanFile opts fpath = scanFileContents opts fpath Nothing---- | Scan source file with contents-scanFileContents :: UpdateMonad m => [String] -> FilePath -> Maybe String -> m ()-scanFileContents opts fpath mcts = Log.scope "file" $ hsdevLiftIO $ do- dbval <- readDB- fpath' <- liftIO $ canonicalizePath fpath- ex <- liftIO $ doesFileExist fpath'- mlocs <- if ex- then do- mloc <- case lookupFile fpath' dbval of- Just m -> return $ view moduleLocation m- Nothing -> do- mproj <- liftIO $ locateProject fpath'- return $ FileModule fpath' mproj- return [(mloc, [], mcts)]- else return []- mapMOf_ (each . _1) (watch . flip watchModule) mlocs- scan- (Cache.loadFiles (== fpath'))- (filterDB (inFile fpath') (const False) . standaloneDB)- mlocs- opts- (scanModules opts)- where- inFile f = maybe False (== f) . preview (moduleIdLocation . moduleFile)---- | Scan cabal modules, doesn't rescan if already scanned-scanCabal :: UpdateMonad m => [String] -> m ()-scanCabal opts = Log.scope "cabal" $ do- dbval <- readDB- let- scannedDbs = databasePackageDbs dbval- unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks userDb- if null unscannedDbs- then Log.sendLog Log.Trace $ "cabal (global-db and user-db) already scanned"- else runTasks $ map (scanPackageDb opts) unscannedDbs---- | Prepare sandbox for scanning. This is used for stack project to build & configure.-prepareSandbox :: UpdateMonad m => Sandbox -> m ()-prepareSandbox sbox@(Sandbox StackWork fpath) = Log.scope "prepare" $ runTasks [- runTask "building dependencies" sbox $ void $ Util.withCurrentDirectory dir $ buildDeps Nothing,- runTask "configuring" sbox $ void $ Util.withCurrentDirectory dir $ configure Nothing]- where- dir = takeDirectory fpath-prepareSandbox _ = return ()---- | Scan sandbox modules, doesn't rescan if already scanned-scanSandbox :: UpdateMonad m => [String] -> Sandbox -> m ()-scanSandbox opts sbox = Log.scope "sandbox" $ do- dbval <- readDB- prepareSandbox sbox- pdbs <- sandboxPackageDbStack sbox- let- scannedDbs = databasePackageDbs dbval- unscannedDbs = filter ((`notElem` scannedDbs) . topPackageDb) $ reverse $ packageDbStacks pdbs- if null unscannedDbs- then Log.sendLog Log.Trace $ "sandbox already scanned"- else runTasks $ map (scanPackageDb opts) unscannedDbs---- | Scan top of package-db stack, usable for rescan-scanPackageDb :: UpdateMonad m => [String] -> PackageDbStack -> m ()-scanPackageDb opts pdbs = runTask "scanning" (topPackageDb pdbs) $ Log.scope "package-db" $ do- watch (\w -> watchPackageDb w pdbs opts)- mlocs <- liftM- (filter (\mloc -> preview modulePackageDb mloc == Just (topPackageDb pdbs))) $- listModules opts pdbs- scan (Cache.loadPackageDb (topPackageDb pdbs)) (packageDbDB (topPackageDb pdbs)) ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do- ms <- browseModules opts pdbs (mlocs' ^.. each . _1)- docs <- liftIO $ hsdevLiftWith (ToolError "hdocs") $ hdocsCabal pdbs opts- updater $ mconcat $ map (fromModule . fmap (setDocs' docs)) ms- where- setDocs' :: Map String (Map String String) -> Module -> Module- setDocs' docs m = maybe m (`setDocs` m) $ M.lookup (T.unpack $ view moduleName m) docs---- | Scan project file-scanProjectFile :: UpdateMonad m => [String] -> FilePath -> m Project-scanProjectFile opts cabal = runTask "scanning" cabal $ do- proj <- S.scanProjectFile opts cabal- updater $ fromProject proj- return proj---- | Scan project and related package-db stack-scanProjectStack :: UpdateMonad m => [String] -> FilePath -> m ()-scanProjectStack opts cabal = do- proj <- scanProjectFile opts cabal- scanProject opts cabal- sbox <- liftIO $ projectSandbox (view projectPath proj)- maybe (scanCabal opts) (scanSandbox opts) sbox---- | Scan project-scanProject :: UpdateMonad m => [String] -> FilePath -> m ()-scanProject opts cabal = runTask "scanning" (project cabal) $ Log.scope "project" $ do- proj <- scanProjectFile opts cabal- watch (\w -> watchProject w proj opts)- S.ScanContents _ [(_, sources)] _ <- S.enumProject proj- scan (Cache.loadProject $ view projectCabal proj) (projectDB proj) sources opts $ scanModules opts---- | Scan directory for source files and projects-scanDirectory :: UpdateMonad m => [String] -> FilePath -> m ()-scanDirectory opts dir = runTask "scanning" dir $ Log.scope "directory" $ do- S.ScanContents standSrcs projSrcs pdbss <- S.enumDirectory dir- runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs]- runTasks $ map (scanPackageDb opts) pdbss -- TODO: Don't rescan- mapMOf_ (each . _1) (watch . flip watchModule) standSrcs- scan (Cache.loadFiles (dir `isParent`)) (filterDB inDir (const False) . standaloneDB) standSrcs opts $ scanModules opts- where- inDir = maybe False (dir `isParent`) . preview (moduleIdLocation . moduleFile)--scanContents :: UpdateMonad m => [String] -> S.ScanContents -> m ()-scanContents opts (S.ScanContents standSrcs projSrcs pdbss) = do- dbval <- readDB- let- projs = databaseProjects dbval ^.. each . projectCabal- pdbs = databasePackageDbs dbval- files = allModules (standaloneDB dbval) ^.. each . moduleLocation . moduleFile- srcs = standSrcs ^.. each . _1 . moduleFile- inSrcs src = src `elem` srcs && src `notElem` files- inFiles = maybe False inSrcs . preview (moduleIdLocation . moduleFile)- mapMOf_ (each . _1 . projectCabal) (\p -> Log.sendLog Log.Trace ("scanning project: {}" ~~ p)) projSrcs- runTasks [scanProject opts (view projectCabal p) | (p, _) <- projSrcs, view projectCabal p `notElem` projs]- mapMOf_ (each . _1 . moduleFile) (\f -> Log.sendLog Log.Trace ("scanning file: {}" ~~ f)) standSrcs- mapMOf_ (each . _1) (watch . flip watchModule) standSrcs- scan (Cache.loadFiles inSrcs) (filterDB inFiles (const False) . standaloneDB) standSrcs opts $ scanModules opts- mapMOf_ each (\s -> Log.sendLog Log.Trace ("scanning package-db: {}" ~~ topPackageDb s)) pdbss- runTasks [scanPackageDb opts pdbs' | pdbs' <- pdbss, topPackageDb pdbs' `notElem` pdbs]---- | Scan docs for inspected modules-scanDocs :: UpdateMonad m => [InspectedModule] -> m ()-scanDocs ims = do- -- w <- liftIO $ ghcWorker ["-haddock"] (return ())- -- w <- askSession sessionGhc- runTasks $ map scanDocs' ims- where- scanDocs' im- | not $ hasTag RefinedDocsTag im = runTask "scanning docs" (view inspectedId im) $ Log.scope "docs" $ do- Log.sendLog Log.Trace $ "Scanning docs for {}" ~~ view inspectedId im- im' <- (liftM (setTag RefinedDocsTag) $ S.scanModify doScan im)- <|> return im- Log.sendLog Log.Trace $ "Docs for {} updated: documented {} declarations" ~~- view inspectedId im' ~~- length (im' ^.. inspectionResult . _Right . moduleDeclarations . each . declarationDocs . _Just)- updater $ fromModule im'- | otherwise = Log.sendLog Log.Trace $ "Docs for {} already scanned" ~~ view inspectedId im- doScan _ _ m = do- w <- askSession sessionGhc- liftIO $ inWorker w $ do- opts' <- getModuleOpts [] m- haddockSession opts'- liftGhc $ inspectDocsGhc opts' m--inferModTypes :: UpdateMonad m => [InspectedModule] -> m ()-inferModTypes = runTasks . map inferModTypes' where- inferModTypes' im- | not $ hasTag InferredTypesTag im = runTask "inferring types" (view inspectedId im) $ Log.scope "docs" $ do- w <- askSession sessionGhc- Log.sendLog Log.Trace $ "Inferring types for {}" ~~ view inspectedId im- im' <- (liftM (setTag InferredTypesTag) $- S.scanModify (\opts _ m -> liftIO (inWorker w (targetSession opts m >> inferTypes opts m Nothing))) im)- <|> return im- Log.sendLog Log.Trace $ "Types for {} inferred" ~~ view inspectedId im- updater $ fromModule im'- | otherwise = Log.sendLog Log.Trace $ "Types for {} already inferred" ~~ view inspectedId im---- | Generic scan function. Reads cache only if data is not already loaded, removes obsolete modules and rescans changed modules.-scan :: UpdateMonad m- => (FilePath -> ExceptT String IO Structured)- -- ^ Read data from cache- -> (Database -> Database)- -- ^ Get data from database- -> [S.ModuleToScan]- -- ^ Actual modules. Other modules will be removed from database- -> [String]- -- ^ Extra scan options- -> ([S.ModuleToScan] -> m ())- -- ^ Function to update changed modules- -> m ()-scan cache' part' mlocs opts act = Log.scope "scan" $ do- dbval <- getCache cache' part'- let- obsolete = filterDB (\m -> view moduleIdLocation m `notElem` (mlocs ^.. each . _1)) (const False) dbval- changed <- liftIO $ S.changedModules dbval opts mlocs- cleaner $ return obsolete- act changed--updateEvent :: ServerMonadBase m => Watched -> Event -> UpdateM m ()-updateEvent (WatchedProject proj projOpts) e- | isSource e = do- Log.sendLog Log.Info $ "File '{file}' in project {proj} changed"- ~~ ("file" ~% view eventPath e)- ~~ ("proj" ~% view projectName proj)- dbval <- readDB- let- opts = fromMaybe [] $ do- m <- lookupFile (view eventPath e) dbval- preview (inspection . inspectionOpts) $ getInspected dbval m- scanFile opts $ view eventPath e- | isCabal e = do- Log.sendLog Log.Info $ "Project {proj} changed"- ~~ ("proj" ~% view projectName proj)- scanProject projOpts $ view projectCabal proj- | otherwise = return ()-updateEvent (WatchedPackageDb pdbs opts) e- | isConf e = do- Log.sendLog Log.Info $ "Package db {package} changed"- ~~ ("package" ~% topPackageDb pdbs)- scanPackageDb opts pdbs- | otherwise = return ()-updateEvent WatchedModule e- | isSource e = do- Log.sendLog Log.Info $ "Module {file} changed"- ~~ ("file" ~% view eventPath e)- dbval <- readDB- let- opts = fromMaybe [] $ do- m <- lookupFile (view eventPath e) dbval- preview (inspection . inspectionOpts) $ getInspected dbval m- scanFile opts $ view eventPath e- | otherwise = return ()--processEvent :: UpdateOptions -> Watched -> Event -> ClientM IO ()-processEvent uopts w e = runUpdate uopts $ updateEvent w e--watch :: SessionMonad m => (Watcher -> IO ()) -> m ()-watch f = do- w <- askSession sessionWatcher- liftIO $ f w+{-# LANGUAGE FlexibleContexts, OverloadedStrings, MultiParamTypeClasses, RankNTypes, TypeOperators #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Database.Update ( + Status(..), Progress(..), Task(..), + UpdateOptions(..), + + UpdateM(..), + runUpdate, + + postStatus, updater, runTask, runTasks, runTasks_, + + scanModules, scanFile, scanFiles, scanFileContents, scanCabal, prepareSandbox, scanSandbox, scanPackageDb, scanProjectFile, scanProjectStack, scanProject, scanDirectory, scanContents, + scanPackageDbStackDocs, scanDocs, + setModTypes, inferModTypes, + scan, + processEvents, updateEvents, applyUpdates, + + module HsDev.Database.Update.Types, + + module HsDev.Watcher, + + module Control.Monad.Except + ) where + +import qualified Control.Concurrent.Async as A +import Control.Concurrent.MVar +import Control.DeepSeq +import Control.Exception (ErrorCall, evaluate, displayException) +import Control.Lens hiding ((.=)) +import Control.Monad.Catch (catch, handle, MonadThrow) +import Control.Monad.Except +import Control.Monad.Reader +import Control.Monad.Writer +import Control.Monad.State (get, modify, evalStateT) +import Data.Aeson +import Data.List (intercalate) +import qualified Data.Map.Strict as M +import qualified Data.Set as S +import Data.Maybe +import Data.Text (Text) +import qualified Data.Text as T +import System.FilePath +import qualified System.Log.Simple as Log + +import HsDev.Error +import qualified HsDev.Database.SQLite as SQLite +import HsDev.Display +import HsDev.Inspect +import HsDev.Inspect.Order +import HsDev.PackageDb +import HsDev.Project +import HsDev.Sandbox +import qualified HsDev.Stack as S +import HsDev.Symbols +import HsDev.Tools.Ghc.Session hiding (wait, evaluate) +import HsDev.Tools.Ghc.Types (fileTypes, TypedExpr) +import HsDev.Tools.Types +import HsDev.Tools.HDocs +import qualified HsDev.Scan as S +import HsDev.Scan.Browse +import HsDev.Util (ordNub, fromJSON') +import qualified HsDev.Util as Util (withCurrentDirectory) +import HsDev.Server.Types (commandNotify, inSessionGhc, FileSource(..)) +import HsDev.Server.Message +import HsDev.Database.Update.Types +import HsDev.Watcher +import Text.Format +import System.Directory.Paths + +onStatus :: UpdateMonad m => m () +onStatus = asks (view (updateOptions . updateTasks)) >>= commandNotify . Notification . toJSON . reverse + +childTask :: UpdateMonad m => Task -> m a -> m a +childTask t = local (over (updateOptions . updateTasks) (t:)) + +runUpdate :: ServerMonadBase m => UpdateOptions -> UpdateM m a -> ClientM m a +runUpdate uopts act = Log.scope "update" $ do + (r, updatedMods) <- withUpdateState uopts $ \ust -> + runWriterT (runUpdateM act' `runReaderT` ust) + Log.sendLog Log.Debug $ "updated {} modules" ~~ length updatedMods + return r + where + act' = do + (r, _) <- listen act + -- (r, mlocs') <- listen act + + -- dbs <- liftM S.unions $ forM mlocs' $ \mloc' -> do + -- mid <- SQLite.lookupModuleLocation mloc' + -- case mid of + -- Nothing -> return (S.empty :: S.Set PackageDb) + -- Just mid' -> liftM (S.fromList . map SQLite.fromOnly) $ SQLite.query (SQLite.toQuery $ SQLite.select_ + -- ["ps.package_db"] + -- ["package_dbs as ps", "modules as m"] + -- ["m.package_name == ps.package_name", "m.package_version == ps.package_version", "m.id == ?"]) (SQLite.Only mid') + + -- If some sourced files depends on currently scanned package-dbs + -- We must resolve them and even rescan if there was errors scanning without + -- dependencies provided (lack of fixities can cause errors inspecting files) + + -- sboxes = databaseSandboxes dbval + -- sboxOf :: Path -> Maybe Sandbox + -- sboxOf fpath = find (pathInSandbox fpath) sboxes + -- projsRows <- SQLite.query_ "select name, cabal, version, ifnull(package_db_stack, json('[]')) from projects;" + -- let + -- projs = [proj' | (proj' SQLite.:. (SQLite.Only (SQLite.JSON projPdbs))) <- projsRows, + -- not (S.null (S.fromList projPdbs `S.intersection` dbs))] + + -- stands = [] + -- -- HOWTO? + -- -- stands = do + -- -- sloc <- dbval ^.. standaloneSlice . modules . moduleId . moduleLocation + -- -- guard $ sboxUpdated $ sboxOf (sloc ^?! moduleFile) + -- -- guard (notElem sloc mlocs') + -- -- return (sloc, dbval ^.. databaseModules . ix sloc . inspection . inspectionOpts . each . unpacked, Nothing) + + -- Log.sendLog Log.Trace $ "updated package-dbs: {}, have to rescan {} projects and {} files" + -- ~~ intercalate ", " (map display $ S.toList dbs) + -- ~~ length projs ~~ length stands + -- (_, rlocs') <- listen $ runTasks_ (scanModules [] stands : [scanProject [] (proj ^. projectCabal) | proj <- projs]) + -- let + -- ulocs' = filter (isJust . preview moduleFile) (ordNub $ mlocs' ++ rlocs') + -- getMods :: (MonadIO m) => m [InspectedModule] + -- getMods = do + -- db' <- liftIO $ readAsync db + -- return $ filter ((`elem` ulocs') . view inspectedKey) $ toList $ view databaseModules db' + + -- FIXME: Now it's broken since `Database` is not used anymore + when (view updateDocs uopts) $ do + Log.sendLog Log.Trace "forking inspecting source docs" + Log.sendLog Log.Warning "not implemented" + -- void $ fork (getMods >>= waiter . mapM_ scanDocs_) + when (view updateInfer uopts) $ do + Log.sendLog Log.Trace "forking inferring types" + Log.sendLog Log.Warning "not implemented" + -- void $ fork (getMods >>= waiter . mapM_ inferModTypes_) + return r + -- scanDocs_ :: UpdateMonad m => InspectedModule -> m () + -- scanDocs_ im = do + -- im' <- (S.scanModify (\opts -> inSessionGhc . liftGhc . inspectDocsGhc opts) im) <|> return im + -- sendUpdateAction $ Log.scope "scan-docs" $ SQLite.updateModule im' + -- inferModTypes_ :: UpdateMonad m => InspectedModule -> m () + -- inferModTypes_ im = do + -- -- TODO: locate sandbox + -- im' <- (S.scanModify infer' im) <|> return im + -- sendUpdateAction $ Log.scope "infer-types" $ SQLite.updateModule im' + -- infer' :: UpdateMonad m => [String] -> Module -> m Module + -- infer' opts m = case preview (moduleId . moduleLocation . moduleFile) m of + -- Nothing -> return m + -- Just _ -> inSessionGhc $ do + -- targetSession opts m + -- inferTypes opts m Nothing + +-- | Post status +postStatus :: UpdateMonad m => Task -> m () +postStatus t = childTask t onStatus + +-- | Mark module as updated +updater :: UpdateMonad m => [ModuleLocation] -> m () +updater mlocs = tell $!! mlocs + +-- | Run one task +runTask :: (Display t, UpdateMonad m, NFData a) => String -> t -> m a -> m a +runTask action subj act = Log.scope "task" $ do + postStatus $ set taskStatus StatusWorking task + x <- childTask task act + x `deepseq` postStatus (set taskStatus StatusOk task) + return x + `catch` + (\e -> postStatus (set taskStatus (StatusError e) task) >> hsdevError e) + where + task = Task { + _taskName = action, + _taskStatus = StatusWorking, + _taskSubjectType = displayType subj, + _taskSubjectName = display subj, + _taskProgress = Nothing } + +-- | Run many tasks with numeration +runTasks :: UpdateMonad m => [m a] -> m [a] +runTasks ts = liftM catMaybes $ zipWithM taskNum [1..] (map noErr ts) where + total = length ts + taskNum n = local setProgress where + setProgress = set (updateOptions . updateTasks . _head . taskProgress) (Just (Progress n total)) + noErr v = hsdevIgnore Nothing (Just <$> v) + +-- | Run many tasks with numeration +runTasks_ :: UpdateMonad m => [m ()] -> m () +runTasks_ = void . runTasks + +-- | Scan modules +scanModules :: UpdateMonad m => [String] -> [S.ModuleToScan] -> m () +scanModules opts ms = Log.scope "scan-modules" $ mapM_ (uncurry scanModules') grouped where + scanModules' mproj ms' = do + pdbs <- maybe (return userDb) (inSessionGhc . getProjectPackageDbStack) mproj + case mproj of + Just proj -> sendUpdateAction $ SQLite.updateProject proj (Just pdbs) + Nothing -> return () + updater $ ms' ^.. each . _1 + defines <- askSession sessionDefines + + let + pload (mloc, mopts, mcts) = runTask "preloading" mloc $ do + mfcts <- maybe (S.getFileContents (mloc ^?! moduleFile)) (const $ return Nothing) mcts + case (mfcts ^? _Just . _1) of + Just tm -> Log.sendLog Log.Trace $ "using edited file contents, mtime = {}" ~~ show tm + Nothing -> return () + let + resetInspection = maybe id (set preloadedTime . fileContentsInspection_ (opts ++ mopts)) $ mfcts ^? _Just . _1 + mcts' = mplus mcts (mfcts ^? _Just . _2) + p <- liftIO $ preload (mloc ^?! moduleFile) defines (opts ++ mopts) mloc mcts' + return $ resetInspection p + + ploaded <- runTasks (map pload ms') + mlocs' <- forM ploaded $ \p -> do + let + mloc = p ^. preloadedId . moduleLocation + inspectedMod = Inspected (p ^. preloadedTime) mloc (tag OnlyHeaderTag) $ Right $ p ^. asModule + sendUpdateAction $ Log.scope "preloaded" $ SQLite.updateModule inspectedMod + return mloc + updater mlocs' + + (sqlMods', sqlAenv') <- do + let + mprojectDeps = SQLite.buildQuery $ SQLite.select_ + ["ps.module_id"] + ["projects_modules_scope as ps"] + ["ps.cabal is ?"] + sqlMods' <- SQLite.loadModules mprojectDeps (SQLite.Only $ mproj ^? _Just . projectCabal) + return (sqlMods', mconcat (map moduleAnalyzeEnv sqlMods')) + + Log.sendLog Log.Trace $ "resolving environment: {} modules" ~~ length sqlMods' + case order ploaded of + Left err -> Log.sendLog Log.Error ("failed order dependencies for files: {}" ~~ show err) + Right ordered -> do + ms'' <- flip evalStateT sqlAenv' $ runTasks (map inspect' ordered) + mlocs'' <- forM ms'' $ \im -> do + sendUpdateAction $ Log.scope "resolved" $ SQLite.updateModule im + return $ im ^. inspectedKey + updater mlocs'' + where + inspect' pmod = runTask "scanning" (pmod ^. preloadedId . moduleLocation) $ Log.scope "module" $ do + aenv <- get + m <- either (hsdevError . InspectError) eval $ analyzePreloaded aenv pmod + modify (mappend (moduleAnalyzeEnv m)) + return $ Inspected (pmod ^. preloadedTime) (m ^. moduleId . moduleLocation) mempty (Right m) + grouped = M.toList $ M.unionsWith (++) [M.singleton (m ^? _1 . moduleProject . _Just) [m] | m <- ms] + eval v = handle onError (v `deepseq` liftIO (evaluate v)) where + onError :: MonadThrow m => ErrorCall -> m a + onError = hsdevError . OtherError . displayException + +-- | Scan source file, resolve dependent modules +scanFile :: UpdateMonad m => [String] -> Path -> m () +scanFile opts fpath = scanFiles [(FileSource fpath Nothing, opts)] + +-- | Scan source files, resolving dependent modules +scanFiles :: UpdateMonad m => [(FileSource, [String])] -> m () +scanFiles fsrcs = runTask "scanning" ("files" :: String) $ Log.scope "files" $ hsdevLiftIO $ do + Log.sendLog Log.Trace $ "scanning {} files" ~~ length fsrcs + fpaths' <- traverse (liftIO . canonicalize) $ map (fileSource . fst) fsrcs + forM_ fpaths' $ \fpath' -> do + ex <- liftIO $ fileExists fpath' + unless ex $ hsdevError $ FileNotFound fpath' + mlocs <- forM fpaths' $ \fpath' -> do + mids <- SQLite.query (SQLite.toQuery $ SQLite.qModuleId `mappend` SQLite.where_ ["mu.file == ?"]) (SQLite.Only fpath') + if length mids > 1 + then return (head mids ^. moduleLocation) + else do + mproj <- locateProjectInfo fpath' + return $ FileModule fpath' mproj + let + filesMods = liftM concat $ forM fpaths' $ \fpath' -> SQLite.query "select m.id, m.file, m.cabal, m.install_dirs, m.package_name, m.package_version, m.installed_name, m.other_location, m.inspection_time, m.inspection_opts from modules as m where m.file == ?;" (SQLite.Only fpath') + scan filesMods [(mloc, opts, mcts) | (mloc, (FileSource _ mcts, opts)) <- zip mlocs fsrcs] [] $ \mlocs' -> do + mapM_ (watch . flip watchModule) (map (view _1) mlocs') + S.ScanContents dmods _ _ <- fmap mconcat $ mapM (S.enumDependent . view (_1 . moduleFile . path)) mlocs' + Log.sendLog Log.Trace $ "dependent modules: {}" ~~ length dmods + scanModules [] (mlocs' ++ dmods) + +-- | Scan source file with contents and resolve dependent modules +scanFileContents :: UpdateMonad m => [String] -> Path -> Maybe Text -> m () +scanFileContents opts fpath mcts = scanFiles [(FileSource fpath mcts, opts)] + +-- | Scan cabal modules, doesn't rescan if already scanned +scanCabal :: UpdateMonad m => [String] -> m () +scanCabal opts = Log.scope "cabal" $ scanPackageDbStack opts userDb + +-- | Prepare sandbox for scanning. This is used for stack project to build & configure. +prepareSandbox :: UpdateMonad m => Sandbox -> m () +prepareSandbox sbox@(Sandbox StackWork fpath) = Log.scope "prepare" $ runTasks_ [ + runTask "building dependencies" sbox $ void $ Util.withCurrentDirectory dir $ inSessionGhc $ S.buildDeps Nothing, + runTask "configuring" sbox $ void $ Util.withCurrentDirectory dir $ inSessionGhc $ S.configure Nothing] + where + dir = takeDirectory $ view path fpath +prepareSandbox _ = return () + +-- | Scan sandbox modules, doesn't rescan if already scanned +scanSandbox :: UpdateMonad m => [String] -> Sandbox -> m () +scanSandbox opts sbox = Log.scope "sandbox" $ do + prepareSandbox sbox + pdbs <- inSessionGhc $ sandboxPackageDbStack sbox + scanPackageDbStack opts pdbs + +-- | Scan top of package-db stack, usable for rescan +scanPackageDb :: UpdateMonad m => [String] -> PackageDbStack -> m () +scanPackageDb opts pdbs = runTask "scanning" (topPackageDb pdbs) $ Log.scope "package-db" $ do + pdbState <- liftIO $ readPackageDb (topPackageDb pdbs) + let + packageDbMods = S.fromList $ concat $ M.elems pdbState + packages' = M.keys pdbState + Log.sendLog Log.Trace $ "package-db state: {} modules" ~~ length packageDbMods + watch (\w -> watchPackageDb w pdbs opts) + + pkgs <- SQLite.query "select package_name, package_version from package_dbs where package_db == ?;" (SQLite.Only $ topPackageDb pdbs) + if S.fromList packages' == S.fromList pkgs + then Log.sendLog Log.Trace $ "nothing changes, all packages the same" + else do + mlocs <- liftM + (filter (`S.member` packageDbMods)) $ + (inSessionGhc $ listModules opts pdbs packages') + Log.sendLog Log.Trace $ "{} modules found" ~~ length mlocs + let + packageDbMods' = SQLite.query "select m.id, m.file, m.cabal, m.install_dirs, m.package_name, m.package_version, m.installed_name, m.other_location, m.inspection_time, m.inspection_opts from modules as m, package_dbs as ps where m.package_name == ps.package_name and m.package_version == ps.package_version and ps.package_db == ?;" (SQLite.Only (topPackageDb pdbs)) + scan packageDbMods' ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do + ms <- inSessionGhc $ browseModules opts pdbs (mlocs' ^.. each . _1) + Log.sendLog Log.Trace $ "scanned {} modules" ~~ length ms + sendUpdateAction $ do + mapM_ SQLite.updateModule ms + SQLite.updatePackageDb (topPackageDb pdbs) (M.keys pdbState) + + when hdocsSupported $ scanPackageDbStackDocs opts pdbs + + updater $ ms ^.. each . inspectedKey + +-- | Scan top of package-db stack, usable for rescan +scanPackageDbStack :: UpdateMonad m => [String] -> PackageDbStack -> m () +scanPackageDbStack opts pdbs = runTask "scanning" pdbs $ Log.scope "package-db-stack" $ do + pdbStates <- liftIO $ mapM readPackageDb (packageDbs pdbs) + let + packageDbMods = S.fromList $ concat $ concatMap M.elems pdbStates + packages' = ordNub $ concatMap M.keys pdbStates + Log.sendLog Log.Trace $ "package-db-stack state: {} modules" ~~ length packageDbMods + watch (\w -> watchPackageDbStack w pdbs opts) + + pkgs <- liftM concat $ forM (packageDbs pdbs) $ \pdb -> SQLite.query "select package_name, package_version from package_dbs where package_db == ?;" (SQLite.Only pdb) + if S.fromList packages' == S.fromList pkgs + then Log.sendLog Log.Trace $ "nothing changes, all packages the same" + else do + mlocs <- liftM + (filter (`S.member` packageDbMods)) $ + (inSessionGhc $ listModules opts pdbs packages') + Log.sendLog Log.Trace $ "{} modules found" ~~ length mlocs + let + packageDbStackMods = liftM concat $ forM (packageDbs pdbs) $ \pdb -> SQLite.query "select m.id, m.file, m.cabal, m.install_dirs, m.package_name, m.package_version, m.installed_name, m.other_location, m.inspection_time, m.inspection_opts from modules as m, package_dbs as ps where m.package_name == ps.package_name and m.package_version == ps.package_version and ps.package_db == ?;" (SQLite.Only pdb) + scan packageDbStackMods ((,,) <$> mlocs <*> pure [] <*> pure Nothing) opts $ \mlocs' -> do + ms <- inSessionGhc $ browseModules opts pdbs (mlocs' ^.. each . _1) + Log.sendLog Log.Trace $ "scanned {} modules" ~~ length ms + sendUpdateAction $ do + mapM_ SQLite.updateModule ms + sequence_ [SQLite.updatePackageDb pdb (M.keys pdbState) | (pdb, pdbState) <- zip (packageDbs pdbs) pdbStates] + + -- BUG: I don't know why, but these steps leads to segfault on my PC: + -- > hsdev scan --cabal --project . + -- > hsdev check -f .\src\HsDev\Client\Commands.hs + -- But it works if docs are scanned, it also works from ghci + + -- needDocs <- asks (view updateDocs) + -- ms' <- if needDocs + -- then do + -- docs <- inSessionGhc $ hdocsCabal pdbs opts + -- return $ map (fmap $ setDocs' docs) ms + -- else return ms + + when hdocsSupported $ scanPackageDbStackDocs opts pdbs + + updater $ ms ^.. each . inspectedKey + +-- | Scan project file +scanProjectFile :: UpdateMonad m => [String] -> Path -> m Project +scanProjectFile opts cabal = runTask "scanning" cabal $ do + proj <- S.scanProjectFile opts cabal + pdbs <- inSessionGhc $ getProjectPackageDbStack proj + sendUpdateAction $ Log.scope "scan-project-file" $ SQLite.updateProject proj (Just pdbs) + return proj + +-- | Refine project info and update if necessary +refineProjectInfo :: UpdateMonad m => Project -> m Project +refineProjectInfo proj = do + [(SQLite.Only exist)] <- SQLite.query "select count(*) > 0 from projects where cabal == ?;" (SQLite.Only (proj ^. projectCabal)) + if exist + then SQLite.loadProject (proj ^. projectCabal) + else runTask "scanning" (proj ^. projectCabal) $ do + proj' <- liftIO $ loadProject proj + pdbs <- inSessionGhc $ getProjectPackageDbStack proj' + sendUpdateAction $ Log.scope "refine-project-info" $ SQLite.updateProject proj' (Just pdbs) + return proj' + +-- | Get project info for module +locateProjectInfo :: UpdateMonad m => Path -> m (Maybe Project) +locateProjectInfo cabal = liftIO (locateProject (view path cabal)) >>= traverse refineProjectInfo + +-- | Scan project and related package-db stack +scanProjectStack :: UpdateMonad m => [String] -> Path -> m () +scanProjectStack opts cabal = do + proj <- scanProjectFile opts cabal + scanProject opts cabal + sbox <- liftIO $ projectSandbox (view projectPath proj) + maybe (scanCabal opts) (scanSandbox opts) sbox + +-- | Scan project +scanProject :: UpdateMonad m => [String] -> Path -> m () +scanProject opts cabal = runTask "scanning" (project $ view path cabal) $ Log.scope "project" $ do + proj <- scanProjectFile opts cabal + watch (\w -> watchProject w proj opts) + S.ScanContents _ [(_, sources)] _ <- S.enumProject proj + let + projMods = SQLite.query "select m.id, m.file, m.cabal, m.install_dirs, m.package_name, m.package_version, m.installed_name, m.other_location, m.inspection_time, m.inspection_opts from modules as m where m.file is not null and m.cabal == ?;" (SQLite.Only $ proj ^. projectCabal) + scan projMods sources opts $ \mlocs' -> do + scanModules opts mlocs' + + -- Scan docs + -- inSessionGhc $ do + -- currentSession >>= maybe (return ()) (const clearTargets) + + -- forM_ (maybe [] targetInfos (proj ^. projectDescription)) $ \tinfo' -> do + -- opts' <- getProjectTargetOpts [] proj (tinfo' ^. targetBuildInfo) + -- files' <- projectTargetFiles proj tinfo' + -- haddockSession opts' + -- docsMap <- liftGhc $ readProjectTargetDocs opts' proj files' + -- Log.sendLog Log.Debug $ "scanned logs for modules: {}, summary docs: {}" ~~ (intercalate "," (M.keys docsMap)) ~~ (sum $ map M.size $ M.elems docsMap) + + +-- | Scan directory for source files and projects +scanDirectory :: UpdateMonad m => [String] -> Path -> m () +scanDirectory opts dir = runTask "scanning" dir $ Log.scope "directory" $ do + S.ScanContents standSrcs projSrcs pdbss <- S.enumDirectory (view path dir) + runTasks_ [scanProject opts (view projectCabal p) | (p, _) <- projSrcs] + runTasks_ $ map (scanPackageDb opts) pdbss -- TODO: Don't rescan + mapMOf_ (each . _1) (watch . flip watchModule) standSrcs + let + standaloneMods = SQLite.query "select m.id, m.file, m.cabal, m.install_dirs, m.package_name, m.package_version, m.installed_name, m.other_location, m.inspection_time, m.inspection_opts from modules as m where m.cabal is null and m.file is not null and m.file like ? escape '\\';" (SQLite.Only $ SQLite.escapeLike dir `T.append` "%") + scan standaloneMods standSrcs opts $ scanModules opts + +scanContents :: UpdateMonad m => [String] -> S.ScanContents -> m () +scanContents opts (S.ScanContents standSrcs projSrcs pdbss) = do + projs <- liftM (map SQLite.fromOnly) $ SQLite.query_ "select cabal from projects;" + pdbs <- liftM (map SQLite.fromOnly) $ SQLite.query_ "select package_db from package_dbs;" + let + filesMods = SQLite.query_ "select m.id, m.file, m.cabal, m.install_dirs, m.package_name, m.package_version, m.installed_name, m.other_location, m.inspection_time, m.inspection_opts from modules as m where m.file is not null and m.cabal is null;" + runTasks_ [scanPackageDb opts pdbs' | pdbs' <- pdbss, topPackageDb pdbs' `notElem` pdbs] + runTasks_ [scanProject opts (view projectCabal p) | (p, _) <- projSrcs, view projectCabal p `notElem` projs] + mapMOf_ (each . _1) (watch . flip watchModule) standSrcs + scan filesMods standSrcs opts $ scanModules opts + +-- | Scan installed docs +scanPackageDbStackDocs :: UpdateMonad m => [String] -> PackageDbStack -> m () +scanPackageDbStackDocs opts pdbs + | hdocsSupported = Log.scope "docs" $ do + docs <- inSessionGhc $ hdocsCabal pdbs opts + Log.sendLog Log.Trace $ "docs scanned: {} packages, {} modules total" + ~~ length docs ~~ sum (map (M.size . snd) docs) + sendUpdateAction $ SQLite.executeMany "update symbols set docs = ? where name == ? and module_id in (select id from modules where name == ? and package_name == ? and package_version == ?);" $ do + (ModulePackage pname pver, pdocs) <- docs + (mname, mdocs) <- M.toList pdocs + (nm, doc) <- M.toList mdocs + return (doc, nm, mname, pname, pver) + Log.sendLog Log.Trace "docs set" + | otherwise = Log.sendLog Log.Warning $ "hdocs not supported" + +-- | Scan docs for inspected modules +scanDocs :: UpdateMonad m => [Module] -> m () +scanDocs + | hdocsSupported = runTasks_ . map scanDocs' + | otherwise = const $ Log.sendLog Log.Warning $ "hdocs not supported" + where + scanDocs' m = runTask "scanning docs" (view (moduleId . moduleLocation) m) $ Log.scope "docs" $ do + mid <- SQLite.lookupModule (m ^. moduleId) + mid' <- maybe (hsdevError $ SQLiteError "module id not found") return mid + m' <- mapMOf (moduleId . moduleLocation . moduleProject . _Just) refineProjectInfo m + Log.sendLog Log.Trace $ "Scanning docs for {}" ~~ view (moduleId . moduleLocation) m' + docsMap <- inSessionGhc $ do + opts' <- getModuleOpts [] m' + currentSession >>= maybe (return ()) (const clearTargets) + -- Calling haddock with targets set sometimes cause errors + haddockSession opts' + readModuleDocs opts' m' + sendUpdateAction $ do + SQLite.executeMany "update symbols set docs = ? where name == ? and module_id == ?;" + [(doc, nm, mid') | (nm, doc) <- maybe [] M.toList docsMap] + SQLite.execute "update modules set tags = json_set(tags, '$.docs', 1) where id == ?;" (SQLite.Only mid') + +-- | Set inferred types for module +setModTypes :: UpdateMonad m => ModuleId -> [Note TypedExpr] -> m () +setModTypes m ts = Log.scope "set-types" $ do + mid <- SQLite.lookupModule m + mid' <- maybe (hsdevError $ SQLiteError "module id not found") return mid + sendUpdateAction $ do + SQLite.executeMany "insert into types (module_id, line, column, line_to, column_to, expr, type) values (?, ?, ?, ?, ?, ?, ?);" [ + (SQLite.Only mid' SQLite.:. view noteRegion n' SQLite.:. view note n') | n' <- ts] + SQLite.execute "update names set inferred_type = (select type from types as t where t.module_id = ? and names.line = t.line and names.column = t.column and names.line_to = t.line_to and names.column_to = t.column_to) where module_id == ?;" + (mid', mid') + SQLite.execute "update symbols set type = (select type from types as t where t.module_id = ? and symbols.line = t.line and symbols.column = t.column order by t.line_to, t.column_to) where module_id == ?;" (mid', mid') + SQLite.execute "update modules set tags = json_set(tags, '$.types', 1) where id == ?;" (SQLite.Only mid') + +-- | Infer types for modules +inferModTypes :: UpdateMonad m => [Module] -> m () +inferModTypes = runTasks_ . map inferModTypes' where + inferModTypes' m = runTask "inferring types" (view (moduleId . moduleLocation) m) $ Log.scope "types" $ do + mid <- SQLite.lookupModule (m ^. moduleId) + _ <- maybe (hsdevError $ SQLiteError "module id not found") return mid + m' <- mapMOf (moduleId . moduleLocation . moduleProject . _Just) refineProjectInfo m + Log.sendLog Log.Trace $ "Inferring types for {}" ~~ view (moduleId . moduleLocation) m' + + mcts <- fmap (fmap snd) $ S.getFileContents (m' ^?! moduleId . moduleLocation . moduleFile) + types' <- inSessionGhc $ do + opts' <- getModuleOpts [] m' + targetSession opts' m' + fileTypes m' mcts + + setModTypes (m' ^. moduleId) types' + +-- | Generic scan function. Removed obsolete modules and calls callback on changed modules. +scan :: UpdateMonad m + => m [(SQLite.Only Int) SQLite.:. ModuleLocation SQLite.:. Inspection] + -- ^ Get affected modules, obsolete will be removed, changed will be updated + -> [S.ModuleToScan] + -- ^ Actual modules, other will be removed + -> [String] + -- ^ Extra scan options + -> ([S.ModuleToScan] -> m ()) + -- ^ Update function + -> m () +scan part' mlocs opts act = Log.scope "scan" $ do + mlocs' <- liftM (M.fromList . map (\((SQLite.Only mid) SQLite.:. (m SQLite.:. i)) -> (m, (mid, i)))) part' + let + obsolete = M.filterWithKey (\k _ -> k `S.notMember` S.fromList (map (^. _1) mlocs)) mlocs' + changed <- S.changedModules (M.map snd mlocs') opts mlocs + sendUpdateAction $ Log.scope "remove-obsolete" $ forM_ (M.elems obsolete) $ SQLite.removeModule . fst + act changed + +processEvents :: ([(Watched, Event)] -> IO ()) -> MVar (A.Async ()) -> MVar [(Watched, Event)] -> [(Watched, Event)] -> ClientM IO () +processEvents handleEvents updaterTask eventsVar evs = Log.scope "event" $ do + Log.sendLog Log.Trace $ "events received: {}" ~~ intercalate ", " (evs ^.. each . _2 . eventPath) + l <- Log.askLog + liftIO $ do + modifyMVar_ eventsVar (return . (++evs)) + modifyMVar_ updaterTask $ \task -> do + done <- fmap isJust $ poll task + if done + then do + Log.withLog l $ Log.sendLog Log.Trace "starting update thread" + A.async $ fix $ \loop -> do + updates <- modifyMVar eventsVar (\es -> return ([], es)) + unless (null updates) $ handleEvents updates >> loop + else return task + +updateEvents :: ServerMonadBase m => [(Watched, Event)] -> UpdateM m () +updateEvents updates = Log.scope "updater" $ do + Log.sendLog Log.Trace $ "prepared to process {} events" ~~ length updates + files <- fmap concat $ forM updates $ \(w, e) -> case w of + WatchedProject proj projOpts + | isSource e -> do + Log.sendLog Log.Info $ "File '{file}' in project {proj} changed" + ~~ ("file" ~% view eventPath e) + ~~ ("proj" ~% view projectName proj) + [SQLite.Only mopts] <- SQLite.query "select inspection_opts from modules where file == ?;" (SQLite.Only $ view eventPath e) + opts <- maybe (return []) (maybe (parseErr' mopts) return . fromJSON') mopts + return [(FileSource (fromFilePath $ view eventPath e) Nothing, opts)] + | isCabal e -> do + Log.sendLog Log.Info $ "Project {proj} changed" + ~~ ("proj" ~% view projectName proj) + scanProject projOpts $ view projectCabal proj + return [] + | otherwise -> return [] + WatchedPackageDb pdbs opts + | isConf e -> do + Log.sendLog Log.Info $ "Package db {package} changed" + ~~ ("package" ~% topPackageDb pdbs) + scanPackageDb opts pdbs + return [] + | otherwise -> return [] + WatchedModule + | isSource e -> do + Log.sendLog Log.Info $ "Module {file} changed" + ~~ ("file" ~% view eventPath e) + [SQLite.Only mopts] <- SQLite.query "select inspection_opts from modules where file == ?;" (SQLite.Only $ view eventPath e) + opts <- maybe (return []) (maybe (parseErr' mopts) return . fromJSON') mopts + return [(FileSource (fromFilePath $ view eventPath e) Nothing, opts)] + | otherwise -> return [] + scanFiles files + where + parseErr' mopts' = do + Log.sendLog Log.Error $ "Error parsing inspection_opts: {}" ~~ show mopts' + hsdevError $ SQLiteError $ "Error parsing inspection_opts: {}" ~~ show mopts' + +applyUpdates :: UpdateOptions -> [(Watched, Event)] -> ClientM IO () +applyUpdates uopts = runUpdate uopts . updateEvents + +watch :: SessionMonad m => (Watcher -> IO ()) -> m () +watch f = do + w <- askSession sessionWatcher + liftIO $ f w
src/HsDev/Database/Update/Types.hs view
@@ -1,8 +1,11 @@ {-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings, FlexibleInstances, MultiParamTypeClasses, TypeFamilies, UndecidableInstances, ConstraintKinds, FlexibleContexts, TemplateHaskell #-} module HsDev.Database.Update.Types ( - Status(..), Progress(..), Task(..), UpdateOptions(..), UpdateM(..), UpdateMonad, - taskName, taskStatus, taskSubjectType, taskSubjectName, taskProgress, updateTasks, updateGhcOpts, updateDocs, updateInfer, + Status(..), Progress(..), Task(..), + UpdateOptions(..), updateTasks, updateGhcOpts, updateDocs, updateInfer, + UpdateState(..), updateOptions, updateWorker, withUpdateState, sendUpdateAction, + UpdateM(..), UpdateMonad, + taskName, taskStatus, taskSubjectType, taskSubjectName, taskProgress, module HsDev.Server.Types ) where @@ -17,13 +20,17 @@ import Control.Monad.Writer import Control.Monad.Trans.Control import Data.Aeson +import Data.Functor import Data.Default import qualified System.Log.Simple as Log +import qualified System.Log.Simple.Monad as Log -import HsDev.Server.Types (ServerMonadBase, Session(..), CommandOptions(..), SessionMonad(..), askSession, CommandMonad(..), ClientM(..)) +import Control.Concurrent.Worker +import HsDev.Database.SQLite +import HsDev.Server.Types hiding (Command(..)) import HsDev.Symbols import HsDev.Types -import HsDev.Util ((.::)) +import HsDev.Util ((.::), logAll) data Status = StatusWorking | StatusOk | StatusError HsDevError @@ -34,8 +41,8 @@ instance FromJSON Status where parseJSON v = msum $ map ($ v) [ - withText "status" $ \t -> guard (t == "working") *> return StatusWorking, - withText "status" $ \t -> guard (t == "ok") *> return StatusOk, + withText "status" $ \t -> guard (t == "working") $> StatusWorking, + withText "status" $ \t -> guard (t == "ok") $> StatusOk, liftM StatusError . parseJSON, fail "invalid status"] @@ -87,11 +94,36 @@ makeLenses ''UpdateOptions -type UpdateMonad m = (CommandMonad m, MonadReader UpdateOptions m, MonadWriter [ModuleLocation] m) +data UpdateState = UpdateState { + _updateOptions :: UpdateOptions, + _updateWorker :: Worker (ServerM IO) } -newtype UpdateM m a = UpdateM { runUpdateM :: ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m)) a } - deriving (Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadMask, Functor, MonadReader UpdateOptions, MonadWriter [ModuleLocation]) +makeLenses ''UpdateState +withUpdateState :: SessionMonad m => UpdateOptions -> (UpdateState -> m a) -> m a +withUpdateState uopts fn = do + session <- getSession + bracket (liftIO $ startWorker (withSession session . Log.component "sqlite" . Log.scope "update") enterTransaction logAll) (liftIO . joinWorker) $ \w -> + fn (UpdateState uopts w) + where + enterTransaction act = do + Log.sendLog Log.Trace "entering sqlite transaction" + transaction_ Immediate $ do + Log.sendLog Log.Debug "updating sql database" + _ <- act + Log.sendLog Log.Debug "sql database updated" + +type UpdateMonad m = (CommandMonad m, MonadReader UpdateState m, MonadWriter [ModuleLocation] m) + +sendUpdateAction :: UpdateMonad m => ServerM IO () -> m () +sendUpdateAction act = do + w <- asks _updateWorker + s <- Log.askScope + liftIO $ inWorker w $ Log.localLog (Log.subLog mempty s) $ act + +newtype UpdateM m a = UpdateM { runUpdateM :: ReaderT UpdateState (WriterT [ModuleLocation] (ClientM m)) a } + deriving (Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadMask, Functor, MonadReader UpdateState, MonadWriter [ModuleLocation]) + instance MonadTrans UpdateM where lift = UpdateM . lift . lift . lift @@ -101,6 +133,7 @@ instance ServerMonadBase m => SessionMonad (UpdateM m) where getSession = UpdateM $ lift $ lift getSession + localSession fn = UpdateM . hoist (hoist (localSession fn)) . runUpdateM instance ServerMonadBase m => CommandMonad (UpdateM m) where getOptions = UpdateM $ lift $ lift getOptions @@ -109,6 +142,6 @@ liftBase = UpdateM . liftBase instance MonadBaseControl b m => MonadBaseControl b (UpdateM m) where - type StM (UpdateM m) a = StM (ReaderT UpdateOptions (WriterT [ModuleLocation] (ClientM m))) a + type StM (UpdateM m) a = StM (ReaderT UpdateState (WriterT [ModuleLocation] (ClientM m))) a liftBaseWith f = UpdateM $ liftBaseWith (\f' -> f (f' . runUpdateM)) restoreM = UpdateM . restoreM
src/HsDev/Display.hs view
@@ -1,55 +1,78 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Display (- Display(..)- ) where--import Control.Lens (view)-import Data.Maybe (fromMaybe)--import Text.Format--import HsDev.PackageDb-import HsDev.Project-import HsDev.Sandbox-import HsDev.Symbols.Location--class Display a where- display :: a -> String- displayType :: a -> String--instance Display PackageDb where- display GlobalDb = "global-db"- display UserDb = "user-db"- display (PackageDb p) = "package-db " ++ p- displayType _ = "package-db"--instance Display ModuleLocation where- display (FileModule f _) = f- display (InstalledModule _ _ n) = n- display (ModuleSource s) = fromMaybe "" s- displayType _ = "module"--instance Display Project where- display = view projectName- displayType _ = "project"--instance Display Sandbox where- display (Sandbox _ fpath) = fpath- displayType (Sandbox CabalSandbox _) = "cabal-sandbox"- displayType (Sandbox StackWork _) = "stack-work"--instance Display FilePath where- display = id- displayType _ = "path"--instance Formattable PackageDb where- formattable = formattable . display--instance Formattable ModuleLocation where- formattable = formattable . display--instance Formattable Project where+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Display ( + Display(..) + ) where + +import Control.Lens (view) +import Data.List (intercalate) +import Data.Text.Lens (unpacked) + +import Text.Format + +import System.Directory.Paths +import HsDev.PackageDb +import HsDev.Project +import HsDev.Sandbox +import HsDev.Symbols.Location +import HsDev.Symbols.Types + +class Display a where + display :: a -> String + displayType :: a -> String + +instance Display PackageDb where + display GlobalDb = "global-db" + display UserDb = "user-db" + display (PackageDb p) = "package-db " ++ display p + displayType _ = "package-db" + +instance Display PackageDbStack where + display = intercalate "/" . map display . packageDbs + displayType _ = "package-db-stack" + +instance Display ModuleLocation where + display (FileModule f _) = display f + display (InstalledModule _ _ n) = view unpacked n + display (OtherLocation s) = view unpacked s + display NoLocation = "<no-location>" + displayType _ = "module" + +instance Display ModuleTag where + display InferredTypesTag = "types" + display RefinedDocsTag = "docs" + display OnlyHeaderTag = "header" + displayType _ = "module-tag" + +instance Display Project where + display = view (projectName . unpacked) + displayType _ = "project" + +instance Display Sandbox where + display (Sandbox _ fpath) = display fpath + displayType (Sandbox CabalSandbox _) = "cabal-sandbox" + displayType (Sandbox StackWork _) = "stack-work" + +instance Display FilePath where + display = id + displayType _ = "path" + +instance Display Path where + display = view path + displayType _ = "path" + +instance Formattable PackageDb where + formattable = formattable . display + +instance Formattable PackageDbStack where + formattable = formattable . display + +instance Formattable ModuleLocation where + formattable = formattable . display + +instance Formattable Project where + formattable = formattable . display + +instance Formattable Sandbox where formattable = formattable . display-
src/HsDev/Error.hs view
@@ -1,63 +1,53 @@-{-# LANGUAGE FlexibleContexts #-}--module HsDev.Error (- hsdevError, hsdevOtherError, hsdevLift, hsdevLiftWith, hsdevCatch, hsdevExcept, hsdevLiftIO, hsdevLiftIOWith, hsdevIgnore,- hsdevHandle, hsdevLog,-- module HsDev.Types- ) where--import Prelude hiding (log)--import Control.Exception (IOException)-import Control.Monad.Catch-import Control.Monad.Except-import Data.String (fromString)-import System.Log.Simple (MonadLog, sendLog, Level)--import HsDev.Types---- | Throw `HsDevError`-hsdevError :: MonadThrow m => HsDevError -> m a-hsdevError = throwM---- | Throw as `OtherError`-hsdevOtherError :: (Exception e, MonadThrow m) => e -> m a-hsdevOtherError = hsdevError . OtherError . displayException---- | Throw as `OtherError`-hsdevLift :: MonadThrow m => ExceptT String m a -> m a-hsdevLift = hsdevLiftWith OtherError---- | Throw as some `HsDevError`-hsdevLiftWith :: MonadThrow m => (String -> HsDevError) -> ExceptT String m a -> m a-hsdevLiftWith ctor act = runExceptT act >>= either (hsdevError . ctor) return--hsdevCatch :: MonadCatch m => m a -> m (Either HsDevError a)-hsdevCatch = try--hsdevExcept :: MonadCatch m => m a -> ExceptT HsDevError m a-hsdevExcept = ExceptT . hsdevCatch---- | Rethrow IO exceptions as `HsDevError`-hsdevLiftIO :: MonadCatch m => m a -> m a-hsdevLiftIO = hsdevLiftIOWith IOFailed---- | Rethrow IO exceptions-hsdevLiftIOWith :: MonadCatch m => (String -> HsDevError) -> m a -> m a-hsdevLiftIOWith ctor act = catch act onError where- onError :: MonadThrow m => IOException -> m a- onError = hsdevError . ctor . displayException---- | Ignore hsdev exception-hsdevIgnore :: MonadCatch m => a -> m a -> m a-hsdevIgnore v act = hsdevCatch act >>= either (const $ return v) return---- | Handle hsdev exception-hsdevHandle :: MonadCatch m => (HsDevError -> m a) -> m a -> m a-hsdevHandle h act = hsdevCatch act >>= either h return---- | Log hsdev exception and rethrow-hsdevLog :: MonadLog m => Level -> m a -> m a-hsdevLog lev act = hsdevCatch act >>= either logError return where- logError e = sendLog lev (fromString $ show e) >> hsdevError e+{-# LANGUAGE FlexibleContexts #-} + +module HsDev.Error ( + hsdevError, hsdevOtherError, hsdevLift, hsdevLiftWith, hsdevCatch, hsdevLiftIO, hsdevLiftIOWith, hsdevIgnore, + hsdevHandle, + + module HsDev.Types + ) where + +import Prelude + +import Control.Exception (IOException) +import Control.Monad.Catch +import Control.Monad.Except + +import HsDev.Types + +-- | Throw `HsDevError` +hsdevError :: MonadThrow m => HsDevError -> m a +hsdevError = throwM + +-- | Throw as `OtherError` +hsdevOtherError :: (Exception e, MonadThrow m) => e -> m a +hsdevOtherError = hsdevError . OtherError . displayException + +-- | Throw as `OtherError` +hsdevLift :: MonadThrow m => ExceptT String m a -> m a +hsdevLift = hsdevLiftWith OtherError + +-- | Throw as some `HsDevError` +hsdevLiftWith :: MonadThrow m => (String -> HsDevError) -> ExceptT String m a -> m a +hsdevLiftWith ctor act = runExceptT act >>= either (hsdevError . ctor) return + +hsdevCatch :: MonadCatch m => m a -> m (Either HsDevError a) +hsdevCatch = try + +-- | Rethrow IO exceptions as `HsDevError` +hsdevLiftIO :: MonadCatch m => m a -> m a +hsdevLiftIO = hsdevLiftIOWith IOFailed + +-- | Rethrow IO exceptions +hsdevLiftIOWith :: MonadCatch m => (String -> HsDevError) -> m a -> m a +hsdevLiftIOWith ctor act = catch act onError where + onError :: MonadThrow m => IOException -> m a + onError = hsdevError . ctor . displayException + +-- | Ignore hsdev exception +hsdevIgnore :: MonadCatch m => a -> m a -> m a +hsdevIgnore v act = hsdevCatch act >>= either (const $ return v) return + +-- | Handle hsdev exception +hsdevHandle :: MonadCatch m => (HsDevError -> m a) -> m a -> m a +hsdevHandle h act = hsdevCatch act >>= either h return
src/HsDev/Inspect.hs view
@@ -1,467 +1,450 @@-{-# LANGUAGE TypeSynonymInstances #-}--module HsDev.Inspect (- analyzeModule, inspectDocsChunk, inspectDocs, inspectDocsGhc,- inspectContents, contentsInspection,- inspectFile, fileInspection,- projectDirs, projectSources,- inspectProject,- getDefines,- preprocess, preprocess_,-- module Control.Monad.Except- ) where--import Control.Arrow-import Control.Applicative-import Control.DeepSeq-import qualified Control.Exception as E-import Control.Lens (view, preview, set, over)-import Control.Lens.At (ix)-import Control.Monad-import Control.Monad.Except-import Data.Char (isSpace)-import Data.Function (on)-import Data.List-import Data.Map (Map)-import Data.Maybe (fromMaybe, mapMaybe, catMaybes, listToMaybe, isJust)-import Data.Ord (comparing)-import Data.String (IsString, fromString)-import Data.Text (Text)-import qualified Data.Text as T (unpack)-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, getPOSIXTime)-import qualified Data.Map as M-import qualified Language.Haskell.Exts as H-import qualified Language.Preprocessor.Cpphs as Cpphs-import qualified System.Directory as Dir-import System.FilePath-import Data.Generics.Uniplate.Data-import HDocs.Haddock--import HsDev.Error-import HsDev.Symbols-import HsDev.Tools.Base-import HsDev.Tools.Ghc.Worker ()-import HsDev.Tools.HDocs (hdocsy, hdocs, hdocsProcess)-import HsDev.Util---- | Analize source contents-analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module-analyzeModule exts file source = case H.parseFileContentsWithMode (parseMode file exts) source' of- H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason- H.ParseOk (H.Module _ (Just (H.ModuleHead _ (H.ModuleName _ mname) _ mexports)) _ imports declarations) -> Right Module {- _moduleName = fromString mname,- _moduleDocs = Nothing,- _moduleLocation = ModuleSource Nothing,- _moduleExports = fmap (concatMap getExports . getSpec) mexports,- _moduleImports = map getImport imports,- _moduleDeclarations = sortDeclarations $ getDecls declarations }- _ -> Left "Unknown module"- where- -- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces- source' = map untab source- untab '\t' = ' '- untab ch = ch- getSpec (H.ExportSpecList _ es) = es---- | Analize source contents-analyzeModule_ :: [String] -> Maybe FilePath -> String -> Either String Module-analyzeModule_ exts file source = do- mname <- parseModuleName source'- return Module {- _moduleName = fromString mname,- _moduleDocs = Nothing,- _moduleLocation = ModuleSource Nothing,- _moduleExports = do- H.PragmasAndModuleHead _ _ mhead <- parseModuleHead' source'- H.ModuleHead _ _ _ mexports <- mhead- H.ExportSpecList _ exports <- mexports- return $ concatMap getExports exports,- _moduleImports = map getImport $ mapMaybe (uncurry parseImport') parts,- _moduleDeclarations = sortDeclarations $ getDecls $ mapMaybe (uncurry parseDecl') parts }- where- parts :: [(Int, String)]- parts = zip offsets (map unlines parts') where- parts' :: [[String]]- parts' = unfoldr break' $ lines source'- offsets = scanl (+) 0 $ map length parts'- break' :: [String] -> Maybe ([String], [String])- break' [] = Nothing- break' (l:ls) = Just $ first (l:) $ span (maybe True isSpace . listToMaybe) ls-- parseModuleName :: String -> Either String String- parseModuleName cts = maybe (Left "match fail") Right $ do- g <- matchRx "^module\\s+([\\w\\.]+)" cts- g 1-- parseDecl' :: Int -> String -> Maybe (H.Decl H.SrcSpanInfo)- parseDecl' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $- H.parseDeclWithMode (parseMode file exts) cts-- parseImport' :: Int -> String -> Maybe (H.ImportDecl H.SrcSpanInfo)- parseImport' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $- H.parseImportDeclWithMode (parseMode file exts) cts-- parseModuleHead' :: String -> Maybe (H.PragmasAndModuleHead H.SrcSpanInfo)- parseModuleHead' = maybeResult . fmap H.unNonGreedy . H.parseWithMode (parseMode file exts)-- maybeResult :: H.ParseResult a -> Maybe a- maybeResult (H.ParseFailed _ _) = Nothing- maybeResult (H.ParseOk r) = Just r-- addOffset :: Int -> H.SrcSpan -> H.SrcSpan- addOffset offset src = src { H.srcSpanStartLine = H.srcSpanStartLine src + offset, H.srcSpanEndLine = H.srcSpanEndLine src + offset }-- -- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces- source' = map untab source- untab '\t' = ' '- untab ch = ch--parseMode :: Maybe FilePath -> [String] -> H.ParseMode-parseMode file exts = H.defaultParseMode {- H.parseFilename = fromMaybe (H.parseFilename H.defaultParseMode) file,- H.baseLanguage = H.Haskell2010,- H.extensions = H.glasgowExts ++ map H.parseExtension exts,- H.fixities = Just H.baseFixities }---- | Get exports-getExports :: H.ExportSpec H.SrcSpanInfo -> [Export]-getExports (H.EModuleContents _ (H.ModuleName _ m)) = [ExportModule $ fromString m]-getExports (H.EVar _ n) = [uncurry ExportName (identOfQName n) ThingNothing]-getExports (H.EAbs _ _ n) = [uncurry ExportName (identOfQName n) ThingNothing]-getExports (H.EThingWith _ _ n ns) = [uncurry ExportName (identOfQName n) $ ThingWith (map toStr ns)] where- toStr :: H.CName H.SrcSpanInfo -> Text- toStr (H.VarName _ cn) = identOfName cn- toStr (H.ConName _ cn) = identOfName cn---- | Get import-getImport :: H.ImportDecl H.SrcSpanInfo -> Import-getImport d = Import- (mname (H.importModule d))- (H.importQualified d)- (mname <$> H.importAs d)- (importLst <$> H.importSpecs d)- (Just $ toPosition $ H.ann d)- where- mname (H.ModuleName _ n) = fromString n- importLst (H.ImportSpecList _ hiding specs) = ImportList hiding $ map impSpec specs- impSpec (H.IVar _ n) = ImportSpec (identOfName n) ThingNothing- impSpec (H.IAbs _ _ n) = ImportSpec (identOfName n) ThingNothing- impSpec (H.IThingAll _ n) = ImportSpec (identOfName n) ThingAll- impSpec (H.IThingWith _ n ns) = ImportSpec (identOfName n) $ ThingWith $ map identOfName (concatMap childrenBi ns :: [H.Name H.SrcSpanInfo])---- | Decl declarations-getDecls :: [H.Decl H.SrcSpanInfo] -> [Declaration]-getDecls decls =- map mergeDecls .- groupBy ((==) `on` view declarationName) .- sortBy (comparing (view declarationName)) $- concatMap getDecl decls ++ concatMap getDef decls- where- mergeDecls :: [Declaration] -> Declaration- mergeDecls [] = error "Impossible"- mergeDecls ds = Declaration- (view declarationName $ head ds)- Nothing- Nothing- (msum $ map (view declarationDocs) ds)- (minimum <$> mapM (view declarationPosition) ds)- (foldr1 mergeInfos $ map (view declaration) ds)-- mergeInfos :: DeclarationInfo -> DeclarationInfo -> DeclarationInfo- mergeInfos (Function ln ld lr) (Function rn rd rr) = Function (ln `mplus` rn) (ld ++ rd) (lr `mplus` rr)- mergeInfos l _ = l---- | Get local binds-getLocalDecls :: H.Decl H.SrcSpanInfo -> [Declaration]-getLocalDecls decl' = concatMap getDecls' binds' where- binds' :: [H.Binds H.SrcSpanInfo]- binds' = universeBi decl'- getDecls' :: H.Binds H.SrcSpanInfo -> [Declaration]- getDecls' (H.BDecls _ decls) = getDecls decls- getDecls' _ = []---- | Get declaration and child declarations-getDecl :: H.Decl H.SrcSpanInfo -> [Declaration]-getDecl decl' = case decl' of- H.TypeSig loc names typeSignature -> [mkFun loc n (Function (Just $ oneLinePrint typeSignature) [] Nothing) | n <- names]- H.TypeDecl loc h _ -> [mkType loc (tyName h) Type (tyArgs h) `withDef` decl']- H.DataDecl loc dataOrNew mctx h cons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getConDecl (tyName h) (tyArgs h)) cons- H.GDataDecl loc dataOrNew mctx h _ gcons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getGConDecl) gcons- H.ClassDecl loc ctx h _ _ -> [mkType loc (tyName h) (Class `withCtx` ctx) (tyArgs h) `withDef` decl']- _ -> []- where- mkType :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> (TypeInfo -> DeclarationInfo) -> [H.TyVarBind H.SrcSpanInfo] -> Declaration- mkType loc n ctor' args = setPosition loc $ decl (identOfName n) $ ctor' $ TypeInfo Nothing (map oneLinePrint args) Nothing []-- withDef :: H.Pretty a => Declaration -> a -> Declaration- withDef tyDecl' tyDef = set (declaration . typeInfo . typeInfoDefinition) (Just $ prettyPrint tyDef) tyDecl'-- withCtx :: (TypeInfo -> DeclarationInfo) -> Maybe (H.Context H.SrcSpanInfo) -> TypeInfo -> DeclarationInfo- withCtx ctor' mctx = ctor' . set typeInfoContext (fmap makeCtx mctx)-- ctor :: H.DataOrNew H.SrcSpanInfo -> TypeInfo -> DeclarationInfo- ctor (H.DataType _) = Data- ctor (H.NewType _) = NewType-- makeCtx ctx = fromString $ oneLinePrint ctx-- addRel :: H.Name H.SrcSpanInfo -> Declaration -> Declaration- addRel n = set (declaration . related) (Just $ identOfName n)-- tyName :: H.DeclHead H.SrcSpanInfo -> H.Name H.SrcSpanInfo- tyName (H.DHead _ n) = n- tyName (H.DHInfix _ _ n) = n- tyName (H.DHParen _ h) = tyName h- tyName (H.DHApp _ h _) = tyName h-- tyArgs :: H.DeclHead H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo]- tyArgs = universeBi---- | Get constructor and record fields declarations-getConDecl :: H.Name H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo] -> H.QualConDecl H.SrcSpanInfo -> [Declaration]-getConDecl t as (H.QualConDecl loc _ _ cdecl) = case cdecl of- H.ConDecl _ n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ cts `tyFun` dataRes) [] Nothing)]- H.InfixConDecl _ ct n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ (ct : [cts]) `tyFun` dataRes) [] Nothing)]- H.RecDecl _ n fields -> mkFun loc n (Function (Just $ oneLinePrint $ map tyField fields `tyFun` dataRes) [] Nothing) : concatMap (getRec loc dataRes) fields- where- dataRes :: H.Type H.SrcSpanInfo- dataRes = foldr (H.TyApp loc . H.TyVar loc . nameOf) (H.TyCon loc (H.UnQual loc t)) as where- nameOf :: H.TyVarBind H.SrcSpanInfo -> H.Name H.SrcSpanInfo- nameOf (H.KindedVar _ n' _) = n'- nameOf (H.UnkindedVar _ n') = n'--tyField :: H.FieldDecl H.SrcSpanInfo -> H.Type H.SrcSpanInfo-tyField (H.FieldDecl _ _ t) = t---- | Get GADT constructor and record fields declarations-getGConDecl :: H.GadtDecl H.SrcSpanInfo -> [Declaration]-getGConDecl (H.GadtDecl loc n mfields r) = mkFun loc n (Function (Just $ oneLinePrint $ map tyField (fromMaybe [] mfields) `tyFun` r) [] Nothing) : concatMap (getRec loc r) (fromMaybe [] mfields)---- | Get record field declaration-getRec :: H.SrcSpanInfo -> H.Type H.SrcSpanInfo -> H.FieldDecl H.SrcSpanInfo -> [Declaration]-getRec loc t (H.FieldDecl _ ns rt) = [mkFun loc n (Function (Just $ oneLinePrint $ H.TyFun loc t rt) [] Nothing) | n <- ns]---- | Get definitions-getDef :: H.Decl H.SrcSpanInfo -> [Declaration]-getDef (H.FunBind _ []) = []-getDef d@(H.FunBind _ (m : _)) = [setPosition (H.ann m) $ decl (identOfName $ identOfMatch m) fun] where- identOfMatch (H.Match _ n _ _ _) = n- identOfMatch (H.InfixMatch _ _ n _ _ _) = n- fun = Function Nothing (getLocalDecls d) Nothing-getDef d@(H.PatBind loc pat _ _) = map (\name -> setPosition loc (decl (identOfName name) (Function Nothing (getLocalDecls d) Nothing))) (names pat) where- names :: H.Pat H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]- names (H.PVar _ n) = [n]- names (H.PNPlusK _ n _) = [n]- names (H.PInfixApp _ l _ r) = names l ++ names r- names (H.PApp _ _ ns) = concatMap names ns- names (H.PTuple _ _ ns) = concatMap names ns- names (H.PList _ ns) = concatMap names ns- names (H.PParen _ n) = names n- names (H.PRec _ _ pf) = concatMap fieldNames pf- names (H.PAsPat _ n ns) = n : names ns- names (H.PWildCard _) = []- names (H.PIrrPat _ n) = names n- names (H.PatTypeSig _ n _) = names n- names (H.PViewPat _ _ n) = names n- names (H.PBangPat _ n) = names n- names _ = []-- fieldNames :: H.PatField H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]- fieldNames (H.PFieldPat _ _ n) = names n- fieldNames (H.PFieldPun _ n) = case n of- H.Qual _ _ n' -> [n']- H.UnQual _ n' -> [n']- _ -> []- fieldNames (H.PFieldWildcard _) = []-getDef _ = []---- | Make function declaration by location, name and function type-mkFun :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> DeclarationInfo -> Declaration-mkFun loc n = setPosition loc . decl (identOfName n)---- | Make function from arguments and result------ @[a, b, c...] `tyFun` r == a `TyFun` b `TyFun` c ... `TyFun` r@-tyFun :: [H.Type H.SrcSpanInfo] -> H.Type H.SrcSpanInfo -> H.Type H.SrcSpanInfo-tyFun as' r' = foldr (H.TyFun H.noSrcSpan) r' as'---- | Get name of qualified name-identOfQName :: H.QName H.SrcSpanInfo -> (Maybe Text, Text)-identOfQName (H.Qual _ (H.ModuleName _ mname) name) = (Just $ fromString mname, identOfName name)-identOfQName (H.UnQual _ name) = (Nothing, identOfName name)-identOfQName (H.Special _ sname) = (Nothing, fromString $ H.prettyPrint sname)---- | Get name of @H.Name@-identOfName :: H.Name H.SrcSpanInfo -> Text-identOfName name = fromString $ case name of- H.Ident _ s -> s- H.Symbol _ s -> s---- | Print something in one line-oneLinePrint :: (H.Pretty a, IsString s) => a -> s-oneLinePrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode---- | Print something-prettyPrint :: (H.Pretty a, IsString s) => a -> s-prettyPrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.PageMode }) mode' where- mode' = H.PPHsMode {- H.classIndent = 4,- H.doIndent = 4,- H.multiIfIndent = 4,- H.caseIndent = 4,- H.letIndent = 4,- H.whereIndent = 4,- H.onsideIndent = 2,- H.spacing = False,- H.layout = H.PPOffsideRule,- H.linePragmas = False }---- | Convert @H.SrcSpanInfo@ to @Position-toPosition :: H.SrcSpanInfo -> Position-toPosition s = Position (H.startLine s) (H.startColumn s)---- | Set @Declaration@ position-setPosition :: H.SrcSpanInfo -> Declaration -> Declaration-setPosition loc = set declarationPosition (Just $ toPosition loc)---- | Adds documentation to declaration-addDoc :: Map String String -> Declaration -> Declaration-addDoc docsMap decl' = set declarationDocs (preview (ix (view declarationName decl')) docsMap') decl' where- docsMap' = M.mapKeys fromString . M.map fromString $ docsMap---- | Adds documentation to all declarations in module-addDocs :: Map String String -> Module -> Module-addDocs docsMap = over moduleDeclarations (map $ addDoc docsMap)---- | Extract files docs and set them to declarations-inspectDocsChunk :: [String] -> [Module] -> IO [Module]-inspectDocsChunk opts ms = hsdevLiftIOWith (ToolError "hdocs") $ do- docsMaps <- hdocsy (map (view moduleLocation) ms) opts- return $ zipWith addDocs docsMaps ms---- | Extract file docs and set them to module declarations-inspectDocs :: [String] -> Module -> IO Module-inspectDocs opts m = do- let- hdocsWorkaround = False- docsMap <- if hdocsWorkaround- then hdocsProcess (fromMaybe (T.unpack $ view moduleName m) (preview (moduleLocation . moduleFile) m)) opts- else liftM Just $ hdocs (view moduleLocation m) opts- return $ maybe id addDocs docsMap m---- | Like @inspectDocs@, but in @Ghc@ monad-inspectDocsGhc :: [String] -> Module -> Ghc Module-inspectDocsGhc opts m = case view moduleLocation m of- FileModule fpath _ -> do- docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ hsdevLift $ readSourcesGhc opts [fpath]- return $ maybe id addDocs docsMap m- _ -> hsdevError $ ModuleNotSource (view moduleLocation m)---- | Inspect contents-inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule-inspectContents name defines opts cts = inspect (ModuleSource $ Just name) (contentsInspection cts opts) $ do- cts' <- lift $ preprocess_ defines exts name cts- analyzed <- ExceptT $ return $ analyzeModule exts (Just name) cts' <|> analyzeModule_ exts (Just name) cts'- return $ set moduleLocation (ModuleSource $ Just name) analyzed- where- exts = mapMaybe flagExtension opts--contentsInspection :: String -> [String] -> ExceptT String IO Inspection-contentsInspection _ _ = return InspectionNone -- crc or smth---- | Inspect file-inspectFile :: [(String, String)] -> [String] -> FilePath -> Maybe String -> IO InspectedModule-inspectFile defines opts file mcts = hsdevLiftIO $ do- proj <- locateProject file- absFilename <- Dir.canonicalizePath file- ex <- Dir.doesFileExist absFilename- unless ex $ hsdevError $ FileNotFound absFilename- inspect (FileModule absFilename proj) ((if isJust mcts then fileContentsInspection else fileInspection) absFilename opts) $ do- -- docsMap <- liftE $ if hdocsWorkaround- -- then hdocsProcess absFilename opts- -- else liftM Just $ hdocs (FileModule absFilename Nothing) opts- forced <- hsdevLiftWith InspectError $ ExceptT $ E.handle onError $ do- analyzed <- liftM (\s -> analyzeModule exts (Just absFilename) s <|> analyzeModule_ exts (Just absFilename) s) $- maybe (readFileUtf8 absFilename >>= preprocess_ defines exts file) return mcts- force analyzed `deepseq` return analyzed- -- return $ setLoc absFilename proj . maybe id addDocs docsMap $ forced- return $ set moduleLocation (FileModule absFilename proj) forced- where- onError :: E.ErrorCall -> IO (Either String Module)- onError = return . Left . show-- exts = mapMaybe flagExtension opts---- | File inspection data-fileInspection :: FilePath -> [String] -> IO Inspection-fileInspection f opts = do- tm <- Dir.getModificationTime f- return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ ordNub opts---- | File contents inspection data-fileContentsInspection :: FilePath -> [String] -> IO Inspection-fileContentsInspection _ opts = do- tm <- getPOSIXTime- return $ InspectionAt tm $ sort $ ordNub opts---- | Enumerate project dirs-projectDirs :: Project -> IO [Extensions FilePath]-projectDirs p = do- p' <- loadProject p- return $ ordNub $ map (fmap (normalise . (view projectPath p' </>))) $ maybe [] sourceDirs $ view projectDescription p'---- | Enumerate project source files-projectSources :: Project -> IO [Extensions FilePath]-projectSources p = do- dirs <- projectDirs p- let- enumCabals = liftM (map takeDirectory . filter cabalFile) . traverseDirectory- dirs' = map (view entity) dirs- -- enum inner projects and dont consider them as part of this project- subProjs <- liftM (delete (view projectPath p) . ordNub . concat) $ triesMap (enumCabals) dirs'- let- enumHs = liftM (filter thisProjectSource) . traverseDirectory- thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs)- liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (enumHs)) dirs---- | Inspect project-inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule])-inspectProject defines opts p = hsdevLiftIO $ do- p' <- loadProject p- srcs <- projectSources p'- modules <- mapM inspectFile' srcs- return (p', catMaybes modules)- where- inspectFile' exts = liftM return (inspectFile defines (opts ++ extensionsOpts exts) (view entity exts) Nothing) <|> return Nothing---- | Get actual defines-getDefines :: IO [(String, String)]-getDefines = E.handle onIO $ do- tmp <- Dir.getTemporaryDirectory- writeFile (tmp </> "defines.hs") ""- _ <- runWait "ghc" ["-E", "-optP-dM", "-cpp", tmp </> "defines.hs"] ""- cts <- readFileUtf8 (tmp </> "defines.hspp")- Dir.removeFile (tmp </> "defines.hs")- Dir.removeFile (tmp </> "defines.hspp")- return $ mapMaybe (\g -> (,) <$> g 1 <*> g 2) $ mapMaybe (matchRx rx) $ lines cts- where- rx = "#define ([^\\s]+) (.*)"- onIO :: E.IOException -> IO [(String, String)]- onIO _ = return []--preprocess :: [(String, String)] -> FilePath -> String -> IO String-preprocess defines fpath cts = do- cts' <- E.catch (Cpphs.cppIfdef fpath defines [] Cpphs.defaultBoolOptions cts) onIOError- return $ unlines $ map snd cts'- where- onIOError :: E.IOException -> IO [(Cpphs.Posn, String)]- onIOError _ = return []--preprocess_ :: [(String, String)] -> [String] -> FilePath -> String -> IO String-preprocess_ defines exts fpath cts- | hasCPP = preprocess defines fpath cts- | otherwise = return cts- where- exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions cts)- hasCPP = H.EnableExtension H.CPP `elem` exts'+{-# LANGUAGE CPP, TypeSynonymInstances, ImplicitParams, TemplateHaskell #-} + +module HsDev.Inspect ( + Preloaded(..), preloadedId, preloadedMode, preloadedModule, asModule, preloadedTime, preloaded, preload, + AnalyzeEnv(..), analyzeEnv, analyzeFixities, analyzeRefine, moduleAnalyzeEnv, + analyzeResolve, analyzePreloaded, + inspectDocs, inspectDocsGhc, + inspectContents, contentsInspection, + inspectFile, sourceInspection, fileInspection, fileContentsInspection, fileContentsInspection_, installedInspection, moduleInspection, + projectDirs, projectSources, + getDefines, + preprocess, preprocess_, + + module Control.Monad.Except + ) where + +import Control.DeepSeq +import qualified Control.Exception as E +import Control.Lens +import Control.Monad +import Control.Monad.State +import Control.Monad.Except +import Data.Data (Data) +import Data.Function (on) +import Data.List +import Data.Map.Strict (Map) +import Data.Maybe (fromMaybe, mapMaybe) +import Data.Ord (comparing) +import Data.String (IsString, fromString) +import Data.Text (Text) +import qualified Data.Text as T +import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, getPOSIXTime, POSIXTime) +import qualified Data.Map.Strict as M +import qualified Language.Haskell.Exts as H +import Language.Haskell.Exts.Fixity +import qualified Language.Haskell.Names as N +import qualified Language.Haskell.Names.Annotated as N +import qualified Language.Haskell.Names.SyntaxUtils as N +import qualified Language.Haskell.Names.Exports as N +import qualified Language.Haskell.Names.Imports as N +import qualified Language.Haskell.Names.ModuleSymbols as N +import qualified Language.Haskell.Names.Open as N +import qualified Language.Preprocessor.Cpphs as Cpphs +import qualified System.Directory as Dir +import System.FilePath +import Text.Format +import Data.Generics.Uniplate.Data + +import HsDev.Display () +import HsDev.Error +import HsDev.Symbols +import HsDev.Symbols.Name (fromModuleName_) +import HsDev.Symbols.Resolve (refineSymbol, refineTable, RefineTable, symbolUniqId) +import HsDev.Symbols.Parsed hiding (file) +import qualified HsDev.Symbols.HaskellNames as HN +import HsDev.Tools.Base +import HsDev.Tools.Ghc.Worker (GhcM) +import HsDev.Tools.HDocs (hdocs, hdocsProcess, readModuleDocs) +import HsDev.Util +import System.Directory.Paths + +-- | Preloaded module with contents and extensions +data Preloaded = Preloaded { + _preloadedId :: ModuleId, + _preloadedMode :: H.ParseMode, + _preloadedModule :: H.Module H.SrcSpanInfo, + -- ^ Loaded module head without declarations + _preloadedTime :: Inspection, + _preloaded :: Text } + +instance NFData Preloaded where + rnf (Preloaded mid _ _ insp cts) = rnf mid `seq` rnf insp `seq` rnf cts + +asModule :: Lens' Preloaded Module +asModule = lens g' s' where + g' p = Module { + _moduleId = _preloadedId p, + _moduleDocs = Nothing, + _moduleImports = map (fromModuleName_ . void . H.importModule) idecls, + _moduleExports = mempty, + _moduleFixities = mempty, + _moduleScope = mempty, + _moduleSource = Just $ fmap (N.Scoped N.None) $ _preloadedModule p } + where + H.Module _ _ _ idecls _ = _preloadedModule p + s' p m = p { + _preloadedId = _moduleId m, + _preloadedModule = maybe (_preloadedModule p) dropScope (_moduleSource m) } + +-- | Preload module - load head and imports to get actual extensions and dependencies +preload :: Text -> [(String, String)] -> [String] -> ModuleLocation -> Maybe Text -> IO Preloaded +preload name defines opts mloc@(FileModule fpath mproj) Nothing = do + cts <- readFileUtf8 (view path fpath) + insp <- fileInspection fpath opts + let + srcExts = fromMaybe (takeDir fpath `withExtensions` mempty) $ do + proj <- mproj + findSourceDir proj fpath + p' <- preload name defines (opts ++ extensionsOpts srcExts) mloc (Just cts) + return $ p' { _preloadedTime = insp } +preload _ _ _ mloc Nothing = hsdevError $ InspectError $ + format "preload called non-sourced module: {}" ~~ mloc +preload name defines opts mloc (Just cts) = do + cts' <- preprocess_ defines exts fpath $ T.map untab cts + pragmas <- parseOk $ H.getTopPragmas (T.unpack cts') + let + fileExts = [H.parseExtension (T.unpack $ fromName_ $ void lang) | H.LanguagePragma _ langs <- pragmas, lang <- langs] + pmode = H.ParseMode { + H.parseFilename = view path fpath, + H.baseLanguage = H.Haskell2010, + H.extensions = ordNub (map H.parseExtension exts ++ fileExts), + H.ignoreLanguagePragmas = False, + H.ignoreLinePragmas = True, + H.fixities = Nothing, + H.ignoreFunctionArity = False } + H.ModuleHeadAndImports l mpragmas mhead mimps <- parseOk $ fmap H.unNonGreedy $ H.parseWithMode pmode (T.unpack cts') + when (H.isNullSpan $ H.srcInfoSpan l) $ hsdevError $ InspectError + (format "Error parsing module head and imports, file {}" ~~ view path fpath) + mname <- case mhead of + Just (H.ModuleHead _ (H.ModuleName _ nm) _ _) -> return $ fromString nm + _ -> hsdevError $ InspectError $ (format "Parsing module head and imports results in empty module name, file {}" ~~ view path fpath) + insp <- fileContentsInspection opts + return $ Preloaded { + _preloadedId = ModuleId mname mloc, + _preloadedMode = pmode, + _preloadedModule = H.Module l mhead mpragmas mimps [], + _preloadedTime = insp, + _preloaded = cts' } + where + fpath = fromMaybe name (mloc ^? moduleFile) + parseOk :: H.ParseResult a -> IO a + parseOk (H.ParseOk v) = return v + parseOk (H.ParseFailed loc err) = hsdevError $ InspectError $ + format "Parse {} failed at {} with: {}" ~~ fpath ~~ show loc ~~ err + untab '\t' = ' ' + untab ch = ch + exts = mapMaybe flagExtension opts + +data AnalyzeEnv = AnalyzeEnv { + _analyzeEnv :: N.Environment, + _analyzeFixities :: M.Map Name H.Fixity, + _analyzeRefine :: RefineTable } + +instance Monoid AnalyzeEnv where + mempty = AnalyzeEnv mempty mempty mempty + AnalyzeEnv lenv lf lt `mappend` AnalyzeEnv renv rf rt = AnalyzeEnv + (mappend lenv renv) + (mappend lf rf) + (mappend lt rt) + +moduleAnalyzeEnv :: Module -> AnalyzeEnv +moduleAnalyzeEnv m = AnalyzeEnv + (environment m) + (m ^. fixitiesMap) + (refineTable (m ^.. exportedSymbols)) + +-- | Resolve module imports/exports/scope +analyzeResolve :: AnalyzeEnv -> Module -> Module +analyzeResolve (AnalyzeEnv env _ rtable) m = case m ^. moduleSource of + Nothing -> m + Just msrc -> over moduleSymbols (refineSymbol stbl) $ m { + _moduleImports = map (fromModuleName_ . void . H.importModule) idecls', + _moduleExports = map HN.fromSymbol $ N.exportedSymbols tbl msrc, + _moduleFixities = [Fixity (void assoc) (fromMaybe 0 pr) (fixName opName) + | H.InfixDecl _ assoc pr ops <- decls', opName <- map getOpName ops], + _moduleScope = M.map (map HN.fromSymbol) tbl, + _moduleSource = Just annotated } + where + getOpName (H.VarOp _ nm) = nm + getOpName (H.ConOp _ nm) = nm + fixName o = H.Qual () (H.ModuleName () (T.unpack $ m ^. moduleId . moduleName)) (void o) + itbl = N.importTable env msrc + tbl = N.moduleTable itbl msrc + syms = set (each . symbolId . symbolModule) (m ^. moduleId) $ + getSymbols decls' + stbl = refineTable syms `mappend` rtable + -- Not using 'annotate' because we already computed needed tables + annotated = H.Module l mhead' mpragmas idecls' decls' + H.Module l mhead mpragmas idecls decls = fmap (\(N.Scoped _ v) -> N.Scoped N.None v) msrc + mhead' = fmap scopeHead mhead + scopeHead (H.ModuleHead lh mname mwarns mexports) = H.ModuleHead lh mname mwarns $ + fmap (N.annotateExportSpecList tbl . dropScope) mexports + idecls' = N.annotateImportDecls mn env (fmap dropScope idecls) + decls' = map (N.annotateDecl (N.initialScope (N.dropAnn mn) tbl) . dropScope) decls + mn = dropScope $ N.getModuleName msrc + +-- | Inspect preloaded module +analyzePreloaded :: AnalyzeEnv -> Preloaded -> Either String Module +analyzePreloaded aenv@(AnalyzeEnv env gfixities _) p = case H.parseFileContentsWithMode (_preloadedMode p') (T.unpack $ _preloaded p') of + H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason + H.ParseOk m -> Right $ analyzeResolve aenv $ Module { + _moduleId = _preloadedId p', + _moduleDocs = Nothing, + _moduleImports = mempty, + _moduleExports = mempty, + _moduleFixities = mempty, + _moduleScope = mempty, + _moduleSource = Just $ fmap (N.Scoped N.None) m } + where + qimps = M.keys $ N.importTable env (_preloadedModule p) + p' = p { _preloadedMode = (_preloadedMode p) { H.fixities = Just (mapMaybe (`M.lookup` gfixities) qimps) } } + +-- | Get top symbols +getSymbols :: [H.Decl Ann] -> [Symbol] +getSymbols decls = + map mergeSymbols . + groupBy ((==) `on` symbolUniqId) . + sortBy (comparing symbolUniqId) $ + concatMap getDecl decls + where + mergeSymbols :: [Symbol] -> Symbol + mergeSymbols [] = error "impossible" + mergeSymbols [s] = s + mergeSymbols ss@(s:_) = Symbol + (view symbolId s) + (msum $ map (view symbolDocs) ss) + (msum $ map (view symbolPosition) ss) + (foldr1 mergeInfo $ map (view symbolInfo) ss) + + mergeInfo :: SymbolInfo -> SymbolInfo -> SymbolInfo + mergeInfo (Function lt) (Function rt) = Function $ lt `mplus` rt + mergeInfo (PatConstructor las lt) (PatConstructor ras rt) = PatConstructor (if null las then ras else las) (lt `mplus` rt) + mergeInfo (Selector lt lp lc) (Selector rt rp rc) + | lt == rt && lp == rp = Selector lt lp (nub $ lc ++ rc) + | otherwise = Selector lt lp lc + mergeInfo l _ = l + + +-- | Get symbols from declarations +getDecl :: H.Decl Ann -> [Symbol] +getDecl decl' = case decl' of + H.TypeDecl _ h _ -> [mkSymbol (tyName h) (Type (tyArgs h) [])] + H.TypeFamDecl _ h _ _ -> [mkSymbol (tyName h) (TypeFam (tyArgs h) [] Nothing)] + H.ClosedTypeFamDecl _ h _ _ _ -> [mkSymbol (tyName h) (TypeFam (tyArgs h) [] Nothing)] + H.DataDecl _ dt mctx h dcons _ -> mkSymbol nm ((getCtor dt) (tyArgs h) (getCtx mctx)) : concatMap (getConDecl nm) dcons where + nm = tyName h + H.GDataDecl _ dt mctx h _ gcons _ -> mkSymbol nm ((getCtor dt) (tyArgs h) (getCtx mctx)) : concatMap (getGConDecl nm) gcons where + nm = tyName h + H.DataFamDecl _ mctx h _ -> [mkSymbol (tyName h) (DataFam (tyArgs h) (getCtx mctx) Nothing)] + H.ClassDecl _ mctx h _ clsDecls -> mkSymbol nm (Class (tyArgs h) (getCtx mctx)) : concatMap (getClassDecl nm) (fromMaybe [] clsDecls) where + nm = tyName h + H.TypeSig _ ns tsig -> [mkSymbol n (Function (Just $ oneLinePrint tsig)) | n <- ns] + H.PatSynSig _ ns mas _ _ t -> [mkSymbol n (PatConstructor (maybe [] (map prp) mas) (Just $ oneLinePrint t)) | n <- ns'] where +#if MIN_VERSION_haskell_src_exts(1,20,0) + ns' = ns +#else + ns' = [ns] +#endif + H.FunBind _ ms -> [mkSymbol (matchName m) (Function Nothing) | m <- ms] where + matchName (H.Match _ n _ _ _) = n + matchName (H.InfixMatch _ _ n _ _ _) = n + H.PatBind _ p _ _ -> [mkSymbol n (Function Nothing) | n <- patNames p] where + patNames :: H.Pat Ann -> [H.Name Ann] + patNames = childrenBi + H.PatSyn _ p _ _ -> case p of + H.PInfixApp _ _ qn _ -> [mkSymbol (qToName qn) (PatConstructor [] Nothing)] + H.PApp _ qn _ -> [mkSymbol (qToName qn) (PatConstructor [] Nothing)] + H.PRec _ qn fs -> mkSymbol (qToName qn) (PatConstructor [] Nothing) : + [mkSymbol (qToName n) (PatSelector Nothing Nothing (prp $ qToName qn)) | n <- (universeBi fs :: [H.QName Ann])] + _ -> [] + where + qToName (H.Qual _ _ n) = n + qToName (H.UnQual _ n) = n + qToName _ = error "invalid qname" + _ -> [] + where + tyName :: H.DeclHead Ann -> H.Name Ann + tyName = head . universeBi + tyArgs :: Data (ast Ann) => ast Ann -> [Text] + tyArgs n = map prp (universeBi n :: [H.TyVarBind Ann]) + getCtx :: Maybe (H.Context Ann) -> [Text] + getCtx mctx = map prp (universeBi mctx :: [H.Asst Ann]) + getCtor (H.DataType _) = Data + getCtor (H.NewType _) = NewType + +getConDecl :: H.Name Ann -> H.QualConDecl Ann -> [Symbol] +getConDecl ptype (H.QualConDecl _ _ _ cdecl) = case cdecl of + H.ConDecl _ n ts -> [mkSymbol n (Constructor (map prp ts) (prp ptype))] + H.InfixConDecl _ lt n rt -> [mkSymbol n (Constructor (map prp [lt, rt]) (prp ptype))] + H.RecDecl _ n fs -> mkSymbol n (Constructor [prp t | H.FieldDecl _ _ t <- fs] (prp ptype)) : + [mkSymbol fn (Selector (Just $ prp ft) (prp ptype) [prp n]) | H.FieldDecl _ fns ft <- fs, fn <- fns] + +getGConDecl :: H.Name Ann -> H.GadtDecl Ann -> [Symbol] +getGConDecl _ (H.GadtDecl _ n Nothing t) = [mkSymbol n (Constructor (map prp as) (prp res))] where + (as, res) = tyFunSplit t + tyFunSplit = go [] where + go as' (H.TyFun _ arg' res') = go (arg' : as') res' + go as' t' = (reverse as', t') +getGConDecl ptype (H.GadtDecl _ n (Just fs) t) = mkSymbol n (Constructor [prp ft | H.FieldDecl _ _ ft <- fs] (prp t)) : + [mkSymbol fn (Selector (Just $ prp ft) (prp ptype) [prp n]) | H.FieldDecl _ fns ft <- fs, fn <- fns] + +getClassDecl :: H.Name Ann -> H.ClassDecl Ann -> [Symbol] +getClassDecl pclass (H.ClsDecl _ (H.TypeSig _ ns tsig)) = [mkSymbol n (Method (Just $ oneLinePrint tsig) (prp pclass)) | n <- ns] +getClassDecl _ _ = [] + +prp :: H.Pretty a => a -> Text +prp = fromString . H.prettyPrint + + +mkSymbol :: H.Name Ann -> SymbolInfo -> Symbol +mkSymbol nm = Symbol (SymbolId (fromName_ $ void nm) (ModuleId (fromString "") noLocation)) Nothing (nm ^? binders . defPos) + + +-- | Print something in one line +oneLinePrint :: (H.Pretty a, IsString s) => a -> s +oneLinePrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode + +-- | Adds documentation to declaration +addDoc :: Map String String -> Symbol -> Symbol +addDoc docsMap sym' = set symbolDocs (preview (ix (view (symbolId . symbolName) sym')) docsMap') sym' where + docsMap' = M.mapKeys fromString . M.map fromString $ docsMap + +-- | Adds documentation to all declarations in module +addDocs :: Map String String -> Module -> Module +addDocs docsMap = over moduleSymbols (addDoc docsMap) + +-- | Extract file docs and set them to module declarations +inspectDocs :: [String] -> Module -> GhcM Module +inspectDocs opts m = do + let + hdocsWorkaround = False + docsMap <- if hdocsWorkaround + then liftIO $ hdocsProcess (fromMaybe (T.unpack $ view (moduleId . moduleName) m) (preview (moduleId . moduleLocation . moduleFile . path) m)) opts + else liftM Just $ hdocs (view (moduleId . moduleLocation) m) opts + return $ maybe id addDocs docsMap m + +-- | Like @inspectDocs@, but in @Ghc@ monad +inspectDocsGhc :: [String] -> Module -> GhcM Module +inspectDocsGhc opts m = do + docsMap <- readModuleDocs opts m + return $ maybe id addDocs docsMap m + +-- | Inspect contents +inspectContents :: Text -> [(String, String)] -> [String] -> Text -> ExceptT String IO InspectedModule +inspectContents name defines opts cts = inspect (OtherLocation name) (contentsInspection cts opts) $ do + p <- lift $ preload name defines opts (OtherLocation name) (Just cts) + analyzed <- ExceptT $ return $ analyzePreloaded mempty p + return $ set (moduleId . moduleLocation) (OtherLocation name) analyzed + +contentsInspection :: Text -> [String] -> ExceptT String IO Inspection +contentsInspection _ _ = return InspectionNone -- crc or smth + +-- | Inspect file +inspectFile :: [(String, String)] -> [String] -> Path -> Maybe Project -> Maybe Text -> IO InspectedModule +inspectFile defines opts file mproj mcts = hsdevLiftIO $ do + absFilename <- canonicalize file + ex <- fileExists absFilename + unless ex $ hsdevError $ FileNotFound absFilename + inspect (FileModule absFilename mproj) (sourceInspection absFilename mcts opts) $ do + -- docsMap <- liftE $ if hdocsWorkaround + -- then hdocsProcess absFilename opts + -- else liftM Just $ hdocs (FileModule absFilename Nothing) opts + forced <- hsdevLiftWith InspectError $ ExceptT $ E.handle onError $ do + p <- preload absFilename defines opts (FileModule absFilename mproj) mcts + return $!! analyzePreloaded mempty p + -- return $ setLoc absFilename mproj . maybe id addDocs docsMap $ forced + return $ set (moduleId . moduleLocation) (FileModule absFilename mproj) forced + where + onError :: E.ErrorCall -> IO (Either String Module) + onError = return . Left . show + +-- | Source inspection data, differs whether there are contents provided +sourceInspection :: Path -> Maybe Text -> [String] -> IO Inspection +sourceInspection f Nothing = fileInspection f +sourceInspection _ (Just _) = fileContentsInspection + +-- | File inspection data +fileInspection :: Path -> [String] -> IO Inspection +fileInspection f opts = do + tm <- Dir.getModificationTime (view path f) + return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ map fromString $ sort $ ordNub opts + +-- | File contents inspection data +fileContentsInspection :: [String] -> IO Inspection +fileContentsInspection opts = fileContentsInspection_ opts <$> getPOSIXTime + +-- | File contents inspection data +fileContentsInspection_ :: [String] -> POSIXTime -> Inspection +fileContentsInspection_ opts tm = InspectionAt tm $ map fromString $ sort $ ordNub opts + +-- | Installed module inspection data, just opts +installedInspection :: [String] -> IO Inspection +installedInspection opts = return $ InspectionAt 0 $ map fromString $ sort $ ordNub opts + +-- | Inspection by module location +moduleInspection :: ModuleLocation -> [String] -> IO Inspection +moduleInspection (FileModule fpath _) = fileInspection fpath +moduleInspection _ = installedInspection + +-- | Enumerate project dirs +projectDirs :: Project -> IO [Extensions Path] +projectDirs p = do + p' <- loadProject p + return $ ordNub $ map (fmap (normPath . (view projectPath p' `subPath`))) $ maybe [] sourceDirs $ view projectDescription p' + +-- | Enumerate project source files +projectSources :: Project -> IO [Extensions Path] +projectSources p = do + dirs <- projectDirs p + let + enumCabals = liftM (map takeDirectory . filter cabalFile) . traverseDirectory + dirs' = map (view (entity . path)) dirs + -- enum inner projects and dont consider them as part of this project + subProjs <- liftM (map fromFilePath . delete (view (projectPath . path) p) . ordNub . concat) $ triesMap (enumCabals) dirs' + let + enumHs = liftM (filter thisProjectSource) . traverseDirectory + thisProjectSource h = haskellSource h && not (any (`isParent` fromFilePath h) subProjs) + liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (liftM (map fromFilePath) . enumHs . view path)) dirs + +-- | Get actual defines +getDefines :: IO [(String, String)] +getDefines = E.handle onIO $ do + tmp <- Dir.getTemporaryDirectory + writeFile (tmp </> "defines.hs") "" + _ <- runWait "ghc" ["-E", "-optP-dM", "-cpp", tmp </> "defines.hs"] "" + cts <- readFileUtf8 (tmp </> "defines.hspp") + Dir.removeFile (tmp </> "defines.hs") + Dir.removeFile (tmp </> "defines.hspp") + return $ mapMaybe (\g -> (,) <$> g 1 <*> g 2) $ mapMaybe (matchRx rx . T.unpack) $ T.lines cts + where + rx = "#define ([^\\s]+) (.*)" + onIO :: E.IOException -> IO [(String, String)] + onIO _ = return [] + +preprocess :: [(String, String)] -> Path -> Text -> IO Text +preprocess defines fpath cts = do + cts' <- E.catch (Cpphs.cppIfdef (view path fpath) defines [] cppOpts (T.unpack cts)) onIOError + return $ T.unlines $ map (fromString . snd) cts' + where + onIOError :: E.IOException -> IO [(Cpphs.Posn, String)] + onIOError _ = return [] + + cppOpts = Cpphs.defaultBoolOptions { + Cpphs.locations = False, + Cpphs.hashline = False + } + +preprocess_ :: [(String, String)] -> [String] -> Path -> Text -> IO Text +preprocess_ defines exts fpath cts + | hasCPP = preprocess defines fpath cts + | otherwise = return cts + where + exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions $ T.unpack cts) + hasCPP = H.EnableExtension H.CPP `elem` exts' + +dropScope :: Functor f => f (N.Scoped l) -> f l +dropScope = fmap (\(N.Scoped _ a) -> a) + +makeLenses ''Preloaded +makeLenses ''AnalyzeEnv
+ src/HsDev/Inspect/Order.hs view
@@ -0,0 +1,39 @@+module HsDev.Inspect.Order ( + order + ) where + +import Control.Lens +import Data.List +import Data.Maybe +import Data.String +import qualified Data.Set as S +import qualified Language.Haskell.Exts as H + +import Data.Deps +import HsDev.Inspect +import HsDev.Symbols.Types +import System.Directory.Paths + +-- | Order source files so that dependencies goes first and we are able to resolve symbols and set fixities +order :: [Preloaded] -> Either (DepsError Path) [Preloaded] +order ps = do + order' <- linearize pdeps + return $ mapMaybe byFile order' + where + pdeps = mconcat $ map getDeps ps + byFile f = find (\p -> (_preloadedId p ^? moduleLocation . moduleFile) == Just f) ps + files = S.fromList $ map _preloadedId ps ^.. each . moduleLocation . moduleFile + getDeps :: Preloaded -> Deps Path + getDeps p = deps mfile [ifile | ifile <- ifiles, S.member ifile files] where + H.Module _ _ _ idecls _ = _preloadedModule p + imods = [fromString iname | H.ModuleName _ iname <- map H.importModule idecls] + mfile = _preloadedId p ^?! moduleLocation . moduleFile + projRoot = _preloadedId p ^? moduleLocation . moduleProject . _Just . projectPath + mroot = fromMaybe + (sourceModuleRoot (view moduleName $ _preloadedId p) mfile) + projRoot + dirs = do + proj <- _preloadedId p ^.. moduleLocation . moduleProject . _Just + i <- fileTargets proj mfile + view infoSourceDirs i + ifiles = [normPath (joinPaths [mroot, dir, importPath imod]) | imod <- imods, dir <- if null dirs then [fromFilePath "."] else dirs]
src/HsDev/PackageDb.hs view
@@ -1,106 +1,63 @@-{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}--module HsDev.PackageDb (- PackageDb(..), packageDb,- PackageDbStack(..), packageDbStack, globalDb, userDb, fromPackageDb, fromPackageDbs,- topPackageDb, packageDbs, packageDbStacks,- isSubStack,-- packageDbOpt, packageDbStackOpts- ) where--import Control.Applicative-import Control.Monad (guard)-import Control.Lens (makeLenses, each)-import Control.DeepSeq (NFData(..))-import Data.Aeson-import Data.List (tails, isSuffixOf)--import System.Directory.Paths-import HsDev.Util ((.::))--data PackageDb = GlobalDb | UserDb | PackageDb { _packageDb :: FilePath } deriving (Eq, Ord, Read, Show)--makeLenses ''PackageDb--instance NFData PackageDb where- rnf GlobalDb = ()- rnf UserDb = ()- rnf (PackageDb p) = rnf p--instance ToJSON PackageDb where- toJSON GlobalDb = "global-db"- toJSON UserDb = "user-db"- toJSON (PackageDb p) = object ["package-db" .= p]--instance FromJSON PackageDb where- parseJSON v = globalP v <|> userP v <|> dbP v where- globalP = withText "global-db" (\s -> guard (s == "global-db") >> return GlobalDb)- userP = withText "user-db" (\s -> guard (s == "user-db") >> return UserDb)- dbP = withObject "package-db" pathP where- pathP obj = PackageDb <$> obj .:: "package-db"--instance Paths PackageDb where- paths _ GlobalDb = pure GlobalDb- paths _ UserDb = pure UserDb- paths f (PackageDb p) = PackageDb <$> f p---- | Stack of PackageDb in reverse order-newtype PackageDbStack = PackageDbStack { _packageDbStack :: [PackageDb] } deriving (Eq, Ord, Read, Show)--makeLenses ''PackageDbStack--instance NFData PackageDbStack where- rnf (PackageDbStack ps) = rnf ps--instance ToJSON PackageDbStack where- toJSON (PackageDbStack ps) = toJSON ps--instance FromJSON PackageDbStack where- parseJSON = fmap PackageDbStack . parseJSON--instance Paths PackageDbStack where- paths f (PackageDbStack ps) = PackageDbStack <$> (each . paths) f ps---- | Global db stack-globalDb :: PackageDbStack-globalDb = PackageDbStack []---- | User db stack-userDb :: PackageDbStack-userDb = PackageDbStack [UserDb]---- | Make package-db from one package-db-fromPackageDb :: FilePath -> PackageDbStack-fromPackageDb = PackageDbStack . return . PackageDb---- | Make package-db stack from paths-fromPackageDbs :: [FilePath] -> PackageDbStack-fromPackageDbs = PackageDbStack . map PackageDb . reverse---- | Get top package-db for package-db stack-topPackageDb :: PackageDbStack -> PackageDb-topPackageDb (PackageDbStack []) = GlobalDb-topPackageDb (PackageDbStack (d:_)) = d---- | Get list of package-db in stack, adds additional global-db at bottom-packageDbs :: PackageDbStack -> [PackageDb]-packageDbs = (GlobalDb :) . reverse . _packageDbStack---- | Get stacks for each package-db in stack-packageDbStacks :: PackageDbStack -> [PackageDbStack]-packageDbStacks = map PackageDbStack . tails . _packageDbStack---- | Is one package-db stack substack of another-isSubStack :: PackageDbStack -> PackageDbStack -> Bool-isSubStack (PackageDbStack l) (PackageDbStack r) = l `isSuffixOf` r---- | Get ghc options for package-db-packageDbOpt :: PackageDb -> String-packageDbOpt GlobalDb = "-global-package-db"-packageDbOpt UserDb = "-user-package-db"-packageDbOpt (PackageDb p) = "-package-db " ++ p---- | Get ghc options for package-db stack-packageDbStackOpts :: PackageDbStack -> [String]-packageDbStackOpts (PackageDbStack ps) = "-no-user-package-db" : map packageDbOpt (reverse ps)+{-# LANGUAGE OverloadedStrings #-} + +module HsDev.PackageDb ( + module HsDev.PackageDb.Types, + + packageDbPath, readPackageDb + ) where + +import Control.Lens +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (listToMaybe) +import Data.Text (pack, unpack) +import Data.Traversable +import Distribution.InstalledPackageInfo +import Distribution.Package +import Distribution.Text (disp) +import System.FilePath + +import HsDev.PackageDb.Types +import HsDev.Error +import HsDev.Symbols.Location +import HsDev.Tools.Base +import HsDev.Util (directoryContents, readFileUtf8) +import System.Directory.Paths + +-- | Get path to package-db +packageDbPath :: PackageDb -> IO Path +packageDbPath GlobalDb = do + out <- fmap lines $ runTool_ "ghc-pkg" ["list", "--global"] + case out of + (fpath:_) -> return $ fromFilePath $ normalise fpath + [] -> hsdevError $ ToolError "ghc-pkg" "empty output, expecting path to global package-db" +packageDbPath UserDb = do + out <- fmap lines $ runTool_ "ghc-pkg" ["list", "--user"] + case out of + (fpath:_) -> return $ fromFilePath $ normalise fpath + [] -> hsdevError $ ToolError "ghc-pkg" "empty output, expecting path to user package db" +packageDbPath (PackageDb fpath) = return fpath + +-- | Read package-db conf files +readPackageDb :: PackageDb -> IO (Map ModulePackage [ModuleLocation]) +readPackageDb pdb = do + p <- packageDbPath pdb + mlibdir <- fmap (listToMaybe . lines) $ runTool_ "ghc" ["--print-libdir"] + confs <- fmap (filter isConf) $ directoryContents (p ^. path) + fmap M.unions $ forM confs $ \conf -> do + cts <- readFileUtf8 conf + case parseInstalledPackageInfo (unpack cts) of + ParseFailed _ -> return M.empty -- FIXME: Should log as warning + ParseOk _ res -> return $ over (each . each . moduleInstallDirs . each) (subst mlibdir) $ listMods res + where + isConf f = takeExtension f == ".conf" + listMods pinfo = M.singleton pname pmods where + pname = ModulePackage + (pack . show . disp . pkgName $ sourcePackageId pinfo) + (pack . show . disp . pkgVersion $ sourcePackageId pinfo) + pmods = map (InstalledModule (map fromFilePath $ libraryDirs pinfo) pname) names + names = map (pack . show . disp) (exposedModules pinfo) ++ map (pack . show . disp) (hiddenModules pinfo) + subst Nothing f = f + subst (Just libdir) f = case splitPaths f of + ("$topdir":rest) -> joinPaths (fromFilePath libdir : rest) + _ -> f
+ src/HsDev/PackageDb/Types.hs view
@@ -0,0 +1,113 @@+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} + +module HsDev.PackageDb.Types ( + PackageDb(..), packageDb, + PackageDbStack(..), packageDbStack, globalDb, userDb, fromPackageDbs, + topPackageDb, packageDbs, packageDbStacks, + isSubStack, + + packageDbOpt, packageDbStackOpts + ) where + +import Control.Applicative +import Control.Monad (guard) +import Control.Lens (makeLenses, each, (^.)) +import Control.DeepSeq (NFData(..)) +import Data.Aeson +import Data.List (tails, isSuffixOf) +import qualified Data.Text as T +import Data.String + +import System.Directory.Paths + +data PackageDb = GlobalDb | UserDb | PackageDb { _packageDb :: Path } deriving (Eq, Ord) + +makeLenses ''PackageDb + +instance NFData PackageDb where + rnf GlobalDb = () + rnf UserDb = () + rnf (PackageDb p) = rnf p + +instance Show PackageDb where + show GlobalDb = "global-db" + show UserDb = "user-db" + show (PackageDb p) = "package-db:" ++ p ^. path + +instance ToJSON PackageDb where + toJSON GlobalDb = "global-db" + toJSON UserDb = "user-db" + toJSON (PackageDb p) = fromString $ "package-db:" ++ p ^. path + +instance FromJSON PackageDb where + parseJSON v = globalP v <|> userP v <|> dbP v where + globalP = withText "global-db" (\s -> guard (s == "global-db") >> return GlobalDb) + userP = withText "user-db" (\s -> guard (s == "user-db") >> return UserDb) + dbP = withText "package-db" $ \s -> case T.stripPrefix "package-db:" s of + Nothing -> fail ("Can't parse package-db: " ++ T.unpack s) + Just p' -> return $ PackageDb p' + +instance Paths PackageDb where + paths _ GlobalDb = pure GlobalDb + paths _ UserDb = pure UserDb + paths f (PackageDb p) = PackageDb <$> paths f p + +-- | Stack of PackageDb in reverse order +newtype PackageDbStack = PackageDbStack { _packageDbStack :: [PackageDb] } deriving (Eq, Ord, Show) + +makeLenses ''PackageDbStack + +instance NFData PackageDbStack where + rnf (PackageDbStack ps) = rnf ps + +instance ToJSON PackageDbStack where + toJSON (PackageDbStack ps) = toJSON ps + +instance FromJSON PackageDbStack where + parseJSON = fmap PackageDbStack . parseJSON + +instance Paths PackageDbStack where + paths f (PackageDbStack ps) = PackageDbStack <$> (each . paths) f ps + +-- | Global db stack +globalDb :: PackageDbStack +globalDb = PackageDbStack [] + +-- | User db stack +userDb :: PackageDbStack +userDb = PackageDbStack [UserDb] + +-- | Make package-db stack from paths +fromPackageDbs :: [Path] -> PackageDbStack +fromPackageDbs = PackageDbStack . map PackageDb . reverse + +-- | Get top package-db for package-db stack +topPackageDb :: PackageDbStack -> PackageDb +topPackageDb (PackageDbStack []) = GlobalDb +topPackageDb (PackageDbStack (d:_)) = d + +-- | Get list of package-db in stack, adds additional global-db at bottom +packageDbs :: PackageDbStack -> [PackageDb] +packageDbs = (GlobalDb :) . reverse . _packageDbStack + +-- | Get stacks for each package-db in stack +packageDbStacks :: PackageDbStack -> [PackageDbStack] +packageDbStacks = map PackageDbStack . tails . _packageDbStack + +-- | Is one package-db stack substack of another +isSubStack :: PackageDbStack -> PackageDbStack -> Bool +isSubStack (PackageDbStack l) (PackageDbStack r) = l `isSuffixOf` r + +-- | Get ghc options for package-db +packageDbOpt :: PackageDb -> String +packageDbOpt GlobalDb = "-global-package-db" +packageDbOpt UserDb = "-user-package-db" +packageDbOpt (PackageDb p) = "-package-db " ++ p ^. path + +-- | Get ghc options for package-db stack +packageDbStackOpts :: PackageDbStack -> [String] +packageDbStackOpts (PackageDbStack ps) + | "-user-package-db" `elem` opts' = opts' + | otherwise = "-no-user-package-db" : opts' + where + opts' = map packageDbOpt (reverse ps)
src/HsDev/Project.hs view
@@ -1,167 +1,197 @@-{-# LANGUAGE CPP #-}--module HsDev.Project (- module HsDev.Project.Types,-- infoSourceDirsDef,- analyzeCabal,- readProject, loadProject,- withExtensions,- infos, inTarget, fileTarget, fileTargets, findSourceDir, sourceDirs,- targetOpts,-- -- * Helpers- showExtension, flagExtension, extensionFlag,- extensionsOpts- ) where--import Control.Arrow-import Control.Lens (Simple, Lens, view, lens)-import Control.Monad.Except-import Data.List-import Data.Maybe-import Distribution.Compiler (CompilerFlavor(GHC))-import qualified Distribution.Package as P-import qualified Distribution.PackageDescription as PD-import Distribution.PackageDescription.Parse-import Distribution.ModuleName (components)-import Distribution.Text (display)-import Language.Haskell.Extension-import System.FilePath--import HsDev.Project.Compat-import HsDev.Project.Types-import HsDev.Error-import HsDev.Util---- | infoSourceDirs lens with default-infoSourceDirsDef :: Simple Lens Info [FilePath]-infoSourceDirsDef = lens get' set' where- get' i = case _infoSourceDirs i of- [] -> ["."]- dirs -> dirs- set' i ["."] = i { _infoSourceDirs = [] }- set' i dirs = i { _infoSourceDirs = dirs }---- | Analyze cabal file-analyzeCabal :: String -> Either String ProjectDescription-analyzeCabal source = case liftM flattenDescr $ parsePackageDesc source of- ParseOk _ r -> Right ProjectDescription {- _projectVersion = showVer $ P.pkgVersion $ PD.package r,- _projectLibrary = fmap toLibrary $ PD.library r,- _projectExecutables = fmap toExecutable $ PD.executables r,- _projectTests = fmap toTest $ PD.testSuites r }- ParseFailed e -> Left $ "Parse failed: " ++ show e- where- toLibrary lib = Library (map components $ PD.exposedModules lib) (toInfo $ PD.libBuildInfo lib)- toExecutable exe = Executable (componentName $ PD.exeName exe) (PD.modulePath exe) (toInfo $ PD.buildInfo exe)- toTest test = Test (componentName $ PD.testName test) (testSuiteEnabled test) (toInfo $ PD.testBuildInfo test)- toInfo info = Info {- _infoDepends = map pkgName (PD.targetBuildDepends info),- _infoLanguage = PD.defaultLanguage info,- _infoExtensions = PD.defaultExtensions info ++ PD.otherExtensions info ++ PD.oldExtensions info,- _infoGHCOptions = fromMaybe [] $ lookup GHC (PD.options info),- _infoSourceDirs = PD.hsSourceDirs info }-- pkgName :: P.Dependency -> String- pkgName (P.Dependency dep _) = P.unPackageName dep-- flattenDescr :: PD.GenericPackageDescription -> PD.PackageDescription- flattenDescr gpkg = pkg {- PD.library = flip fmap mlib $ flattenCondTree- (insertInfo PD.libBuildInfo (\i l -> l { PD.libBuildInfo = i })),- PD.executables = flip fmap mexes $- second (flattenCondTree (insertInfo PD.buildInfo (\i l -> l { PD.buildInfo = i }))) >>>- (\(n, e) -> e { PD.exeName = n }),- PD.testSuites = flip fmap mtests $- second (flattenCondTree (insertInfo PD.testBuildInfo (\i l -> l { PD.testBuildInfo = i }))) >>>- (\(n, t) -> t { PD.testName = n }) }- where- pkg = PD.packageDescription gpkg- mlib = PD.condLibrary gpkg- mexes = PD.condExecutables gpkg- mtests = PD.condTestSuites gpkg-- insertInfo :: (a -> PD.BuildInfo) -> (PD.BuildInfo -> a -> a) -> [P.Dependency] -> a -> a- insertInfo f s deps' x = s ((f x) { PD.targetBuildDepends = deps' }) x---- | Read project info from .cabal-readProject :: FilePath -> IO Project-readProject file = do- source <- readFile file- length source `seq` either (hsdevError . InspectCabalError file) (return . mkProject) $ analyzeCabal source- where- mkProject desc = (project file) {- _projectDescription = Just desc }---- | Load project description-loadProject :: Project -> IO Project-loadProject p- | isJust (_projectDescription p) = return p- | otherwise = readProject (_projectCabal p)---- | Extensions for target-withExtensions :: a -> Info -> Extensions a-withExtensions x i = Extensions {- _extensions = _infoExtensions i,- _ghcOptions = _infoGHCOptions i,- _entity = x }---- | Returns build targets infos-infos :: ProjectDescription -> [Info]-infos p =- maybe [] (return . _libraryBuildInfo) (_projectLibrary p) ++- map _executableBuildInfo (_projectExecutables p) ++- map _testBuildInfo (_projectTests p)---- | Check if source related to target, source must be relative to project directory-inTarget :: FilePath -> Info -> Bool-inTarget src info = any (`isParent` src) $ view infoSourceDirsDef info---- | Get first target for source file-fileTarget :: Project -> FilePath -> Maybe Info-fileTarget p f = listToMaybe $ fileTargets p f---- | Get possible targets for source file--- There can be many candidates in case of module related to several executables or tests-fileTargets :: Project -> FilePath -> [Info]-fileTargets p f = case filter ((`isSuffixOf` f') . normalise . _executablePath) exes of- [] -> filter (f' `inTarget`) $ maybe [] infos $ _projectDescription p- exes' -> map _executableBuildInfo exes'- where- f' = makeRelative (_projectPath p) f- exes = maybe [] _projectExecutables $ _projectDescription p---- | Finds source dir file belongs to-findSourceDir :: Project -> FilePath -> Maybe (Extensions FilePath)-findSourceDir p f = do- info <- listToMaybe $ fileTargets p f- fmap (`withExtensions` info) $ listToMaybe $ filter (`isParent` f) $ map (_projectPath p </>) $ view infoSourceDirsDef info---- | Returns source dirs for library, executables and tests-sourceDirs :: ProjectDescription -> [Extensions FilePath]-sourceDirs = ordNub . concatMap dirs . infos where- dirs i = map (`withExtensions` i) $ view infoSourceDirsDef i---- | Get options for specific target-targetOpts :: Info -> [String]-targetOpts info' = concat [- ["-i" ++ s | s <- _infoSourceDirs info'],- extensionsOpts $ withExtensions () info',- ["-package " ++ p | p <- _infoDepends info']]---- | Extension as flag name-showExtension :: Extension -> String-showExtension = display---- | Convert -Xext to ext-flagExtension :: String -> Maybe String-flagExtension = stripPrefix "-X"---- | Convert ext to -Xext-extensionFlag :: String -> String-extensionFlag = ("-X" ++)---- | Extensions as opts to GHC-extensionsOpts :: Extensions a -> [String]-extensionsOpts e = map (extensionFlag . showExtension) (_extensions e) ++ _ghcOptions e+{-# LANGUAGE OverloadedStrings, CPP #-} + +module HsDev.Project ( + module HsDev.Project.Types, + + infoSourceDirsDef, targetFiles, projectTargetFiles, + analyzeCabal, + readProject, loadProject, + withExtensions, + fileInTarget, fileTarget, fileTargets, findSourceDir, sourceDirs, + targetOpts, + + -- * Helpers + showExtension, flagExtension, extensionFlag, + extensionsOpts + ) where + +import Control.Arrow +import Control.Lens hiding ((.=), (%=), (<.>), set') +import Control.Monad.Except +import Control.Monad.Loops +import Data.List +import Data.Maybe +import Data.Text (Text, pack, unpack) +import qualified Data.Text as T (intercalate) +import Data.Text.Lens (unpacked) +import Distribution.Compiler (CompilerFlavor(GHC)) +import qualified Distribution.Package as P +import qualified Distribution.PackageDescription as PD +import qualified Distribution.ModuleName as PD (toFilePath) +import Distribution.PackageDescription.Parse +import Distribution.ModuleName (components) +import Distribution.Text (display) +import Language.Haskell.Extension +import System.FilePath +import System.Log.Simple hiding (Level(..)) +import qualified System.Log.Simple as Log (Level(..)) +import Text.Format + +import System.Directory.Paths +import HsDev.Project.Compat +import HsDev.Project.Types +import HsDev.Error +import HsDev.Util + +-- | infoSourceDirs lens with default +infoSourceDirsDef :: Lens' Info [Path] +infoSourceDirsDef = lens get' set' where + get' i = case _infoSourceDirs i of + [] -> ["."] + dirs -> dirs + set' i ["."] = i { _infoSourceDirs = [] } + set' i dirs = i { _infoSourceDirs = dirs } + +-- | Get all source file names of target without prepending them with source-dirs +targetFiles :: Target t => t -> [Path] +targetFiles target' = concat [ + maybeToList (targetMain target'), + map toFile $ targetModules target', + map toFile $ target' ^.. buildInfo . infoOtherModules . each] + where + toFile ps = fromFilePath (joinPath (ps ^.. each . unpacked) <.> "hs") + +-- | Get all source file names relative to project root +projectTargetFiles :: (MonadLog m, Target t) => Project -> t -> m [Path] +projectTargetFiles proj t = do + liftM concat $ forM files $ \file' -> do + candidate <- liftIO $ firstM (fileExists . absolutise (proj ^. projectPath)) [subPath srcDir file' | srcDir <- srcDirs] + case candidate of + Nothing -> do + sendLog Log.Warning $ "Unable to locate source file: {} in source-dirs: {}" ~~ file' ~~ (T.intercalate ", " srcDirs) + return [] + Just file'' -> return [normPath file''] + where + files = targetFiles t + srcDirs = t ^.. buildInfo . infoSourceDirsDef . each + +-- | Analyze cabal file +analyzeCabal :: String -> Either String ProjectDescription +analyzeCabal source = case liftM flattenDescr $ parsePackageDesc source of + ParseOk _ r -> Right ProjectDescription { + _projectVersion = pack $ showVer $ P.pkgVersion $ PD.package r, + _projectLibrary = fmap toLibrary $ PD.library r, + _projectExecutables = fmap toExecutable $ PD.executables r, + _projectTests = fmap toTest $ PD.testSuites r } + ParseFailed e -> Left $ "Parse failed: " ++ show e + where + toLibrary lib = Library (map (map pack . components) $ PD.exposedModules lib) (toInfo $ PD.libBuildInfo lib) + toExecutable exe = Executable (componentName $ PD.exeName exe) (fromFilePath $ PD.modulePath exe) (toInfo $ PD.buildInfo exe) + toTest test = Test (componentName $ PD.testName test) (testSuiteEnabled test) (fmap fromFilePath mainFile) (toInfo $ PD.testBuildInfo test) where + mainFile = case PD.testInterface test of + PD.TestSuiteExeV10 _ fpath -> Just fpath + PD.TestSuiteLibV09 _ mname -> Just $ PD.toFilePath mname + _ -> Nothing + toInfo info = Info { + _infoDepends = map pkgName (PD.targetBuildDepends info), + _infoLanguage = PD.defaultLanguage info, + _infoExtensions = PD.defaultExtensions info ++ PD.otherExtensions info ++ PD.oldExtensions info, + _infoGHCOptions = maybe [] (map pack) $ lookup GHC (PD.options info), + _infoSourceDirs = map pack $ PD.hsSourceDirs info, + _infoOtherModules = map (map pack . components) (PD.otherModules info) } + + pkgName :: P.Dependency -> Text + pkgName (P.Dependency dep _) = pack $ P.unPackageName dep + + flattenDescr :: PD.GenericPackageDescription -> PD.PackageDescription + flattenDescr gpkg = pkg { + PD.library = flip fmap mlib $ flattenCondTree + (insertInfo PD.libBuildInfo (\i l -> l { PD.libBuildInfo = i })), + PD.executables = flip fmap mexes $ + second (flattenCondTree (insertInfo PD.buildInfo (\i l -> l { PD.buildInfo = i }))) >>> + (\(n, e) -> e { PD.exeName = n }), + PD.testSuites = flip fmap mtests $ + second (flattenCondTree (insertInfo PD.testBuildInfo (\i l -> l { PD.testBuildInfo = i }))) >>> + (\(n, t) -> t { PD.testName = n }) } + where + pkg = PD.packageDescription gpkg + mlib = PD.condLibrary gpkg + mexes = PD.condExecutables gpkg + mtests = PD.condTestSuites gpkg + + insertInfo :: (a -> PD.BuildInfo) -> (PD.BuildInfo -> a -> a) -> [P.Dependency] -> a -> a + insertInfo f s deps' x = s ((f x) { PD.targetBuildDepends = deps' }) x + +-- | Read project info from .cabal +readProject :: FilePath -> IO Project +readProject file' = do + source <- readFile file' + length source `seq` either (hsdevError . InspectCabalError file') (return . mkProject) $ analyzeCabal source + where + mkProject desc = (project file') { + _projectDescription = Just desc } + +-- | Load project description +loadProject :: Project -> IO Project +loadProject p + | isJust (_projectDescription p) = return p + | otherwise = readProject (_projectCabal p ^. path) + +-- | Extensions for target +withExtensions :: a -> Info -> Extensions a +withExtensions x i = Extensions { + _extensions = _infoExtensions i, + _ghcOptions = _infoGHCOptions i, + _entity = x } + +-- | Check if source related to target, source must be relative to project directory +fileInTarget :: Path -> Info -> Bool +fileInTarget src info = any (`isParent` src) $ view infoSourceDirsDef info + +-- | Get first target for source file +fileTarget :: Project -> Path -> Maybe Info +fileTarget p f = listToMaybe $ fileTargets p f + +-- | Get possible targets for source file +-- There can be many candidates in case of module related to several executables or tests +fileTargets :: Project -> Path -> [Info] +fileTargets p f = case filter ((`isParent` f') . view executablePath) exes of + [] -> filter (f' `fileInTarget`) (p ^.. projectDescription . _Just . infos) + exes' -> map _executableBuildInfo exes' + where + f' = relPathTo (_projectPath p) f + exes = p ^. projectDescription . _Just . projectExecutables + +-- | Finds source dir file belongs to +findSourceDir :: Project -> Path -> Maybe (Extensions Path) +findSourceDir p f = do + info <- listToMaybe $ fileTargets p f + fmap (`withExtensions` info) $ listToMaybe $ filter (`isParent` f) $ map (_projectPath p `subPath`) (info ^. infoSourceDirsDef) + +-- | Returns source dirs for library, executables and tests +sourceDirs :: ProjectDescription -> [Extensions Path] +sourceDirs = ordNub . concatMap dirs . toListOf infos where + dirs i = map (`withExtensions` i) (i ^. infoSourceDirsDef) + +-- | Get options for specific target +targetOpts :: Info -> [String] +targetOpts info' = concat [ + ["-i" ++ unpack s | s <- _infoSourceDirs info'], + extensionsOpts $ withExtensions () info', + ["-package " ++ unpack p | p <- _infoDepends info']] + +-- | Extension as flag name +showExtension :: Extension -> String +showExtension = display + +-- | Convert -Xext to ext +flagExtension :: String -> Maybe String +flagExtension = stripPrefix "-X" + +-- | Convert ext to -Xext +extensionFlag :: String -> String +extensionFlag = ("-X" ++) + +-- | Extensions as opts to GHC +extensionsOpts :: Extensions a -> [String] +extensionsOpts e = map (extensionFlag . showExtension) (_extensions e) ++ map unpack (_ghcOptions e)
src/HsDev/Project/Compat.hs view
@@ -1,62 +1,63 @@-{-# LANGUAGE CPP #-}--module HsDev.Project.Compat (- showVer, componentName, testSuiteEnabled,- flattenCondTree,- parsePackageDesc- ) where--import Data.Maybe (maybeToList)-import qualified Distribution.PackageDescription as PD-import Distribution.PackageDescription.Parse-import Distribution.Version (Version)-import Distribution.Text (display)-#if MIN_VERSION_Cabal(2,0,0)-import Distribution.Types.CondTree-#else -import Distribution.PackageDescription (CondTree(..))-#endif--#if MIN_VERSION_Cabal(2,0,0)-import Distribution.Types.UnqualComponentName-#else-import Data.Version (showVersion)-#endif--showVer :: Version -> String-#if MIN_VERSION_Cabal(2,0,0)-showVer = display-#else-showVer = showVersion-#endif--#if MIN_VERSION_Cabal(2,0,0)-componentName :: UnqualComponentName -> String-componentName = unUnqualComponentName-#else-componentName :: String -> String-componentName = id-#endif--testSuiteEnabled :: PD.TestSuite -> Bool-#if MIN_VERSION_Cabal(2,0,0)-testSuiteEnabled _ = True-#else-testSuiteEnabled = PD.testEnabled-#endif--flattenCondTree :: Monoid a => (c -> a -> a) -> CondTree v c a -> a-flattenCondTree f (PD.CondNode x cs cmps) = f cs x `mappend` mconcat (concatMap flattenBranch cmps) where-#if MIN_VERSION_Cabal(2,0,0)- flattenBranch (CondBranch _ t mb) = go t mb-#else- flattenBranch (_, t, mb) = go t mb-#endif- go t mb = flattenCondTree f t : map (flattenCondTree f) (maybeToList mb)--parsePackageDesc :: String -> ParseResult PD.GenericPackageDescription-#if MIN_VERSION_Cabal(2,0,0)-parsePackageDesc = parseGenericPackageDescription-#else-parsePackageDesc = parsePackageDescription-#endif+{-# LANGUAGE CPP #-} + +module HsDev.Project.Compat ( + showVer, componentName, testSuiteEnabled, + flattenCondTree, + parsePackageDesc + ) where + +import Data.Maybe (maybeToList) +import Data.Text (Text, pack) +import qualified Distribution.PackageDescription as PD +import Distribution.PackageDescription.Parse +import Distribution.Version (Version) +import Distribution.Text (display) +#if MIN_VERSION_Cabal(2,0,0) +import Distribution.Types.CondTree +#else +import Distribution.PackageDescription (CondTree(..)) +#endif + +#if MIN_VERSION_Cabal(2,0,0) +import Distribution.Types.UnqualComponentName +#else +import Data.Version (showVersion) +#endif + +showVer :: Version -> String +#if MIN_VERSION_Cabal(2,0,0) +showVer = display +#else +showVer = showVersion +#endif + +#if MIN_VERSION_Cabal(2,0,0) +componentName :: UnqualComponentName -> Text +componentName = pack . unUnqualComponentName +#else +componentName :: String -> Text +componentName = pack +#endif + +testSuiteEnabled :: PD.TestSuite -> Bool +#if MIN_VERSION_Cabal(2,0,0) +testSuiteEnabled _ = True +#else +testSuiteEnabled = PD.testEnabled +#endif + +flattenCondTree :: Monoid a => (c -> a -> a) -> CondTree v c a -> a +flattenCondTree f (PD.CondNode x cs cmps) = f cs x `mappend` mconcat (concatMap flattenBranch cmps) where +#if MIN_VERSION_Cabal(2,0,0) + flattenBranch (CondBranch _ t mb) = go t mb +#else + flattenBranch (_, t, mb) = go t mb +#endif + go t mb = flattenCondTree f t : map (flattenCondTree f) (maybeToList mb) + +parsePackageDesc :: String -> ParseResult PD.GenericPackageDescription +#if MIN_VERSION_Cabal(2,0,0) +parsePackageDesc = parseGenericPackageDescription +#else +parsePackageDesc = parsePackageDescription +#endif
src/HsDev/Project/Types.hs view
@@ -1,298 +1,352 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}--module HsDev.Project.Types (- Project(..), projectName, projectPath, projectCabal, projectDescription, project, absolutiseProjectPaths, relativiseProjectPaths,- ProjectDescription(..), projectVersion, projectLibrary, projectExecutables, projectTests,- Target(..),- Library(..), libraryModules, libraryBuildInfo,- Executable(..), executableName, executablePath, executableBuildInfo,- Test(..), testName, testEnabled, testBuildInfo,- Info(..), infoDepends, infoLanguage, infoExtensions, infoGHCOptions, infoSourceDirs,- Extensions(..), extensions, ghcOptions, entity,- ) where--import Control.Arrow-import Control.DeepSeq (NFData(..))-import Control.Lens (makeLenses)-import Data.Aeson-import Data.Aeson.Types (Parser)-import Data.List-import Data.Maybe-import Data.Monoid-import Data.Ord-import Distribution.Text (display, simpleParse)-import qualified Distribution.Text (Text)-import Language.Haskell.Extension-import Text.Format-import System.FilePath--import System.Directory.Paths-import HsDev.Util---- | Cabal project-data Project = Project {- _projectName :: String,- _projectPath :: FilePath,- _projectCabal :: FilePath,- _projectDescription :: Maybe ProjectDescription }- deriving (Read)--instance NFData Project where- rnf (Project n p c _) = rnf n `seq` rnf p `seq` rnf c--instance Eq Project where- l == r = _projectCabal l == _projectCabal r--instance Ord Project where- compare l r = compare (_projectName l, _projectCabal l) (_projectName r, _projectCabal r)--instance Show Project where- show p = unlines $ [- "project " ++ _projectName p,- "\tcabal: " ++ _projectCabal p,- "\tdescription:"] ++ concatMap (map (tab 2) . lines . show) (maybeToList $ _projectDescription p)--instance ToJSON Project where- toJSON p = object [- "name" .= _projectName p,- "path" .= _projectPath p,- "cabal" .= _projectCabal p,- "description" .= _projectDescription p]--instance FromJSON Project where- parseJSON = withObject "project" $ \v -> Project <$>- v .:: "name" <*>- v .:: "path" <*>- v .:: "cabal" <*>- v .:: "description"--instance Paths Project where- paths f (Project nm p c desc) = Project nm <$> f p <*> f c <*> traverse (paths f) desc---- | Make project by .cabal file-project :: FilePath -> Project-project file = Project {- _projectName = takeBaseName (takeDirectory cabal),- _projectPath = takeDirectory cabal,- _projectCabal = cabal,- _projectDescription = Nothing }- where- file' = dropTrailingPathSeparator $ normalise file- cabal- | takeExtension file' == ".cabal" = file'- | otherwise = file' </> (takeBaseName file' <.> "cabal")---- | Make paths absolute, not relative-absolutiseProjectPaths :: Project -> Project-absolutiseProjectPaths proj = absolutise (_projectPath proj) proj---- | Make paths relative-relativiseProjectPaths :: Project -> Project-relativiseProjectPaths proj = relativise (_projectPath proj) proj--data ProjectDescription = ProjectDescription {- _projectVersion :: String,- _projectLibrary :: Maybe Library,- _projectExecutables :: [Executable],- _projectTests :: [Test] }- deriving (Eq, Read)--instance Show ProjectDescription where- show pd = unlines $- concatMap (lines . show) (maybeToList (_projectLibrary pd)) ++- concatMap (lines . show) (_projectExecutables pd) ++- concatMap (lines . show) (_projectTests pd)--instance ToJSON ProjectDescription where- toJSON d = object [- "version" .= _projectVersion d,- "library" .= _projectLibrary d,- "executables" .= _projectExecutables d,- "tests" .= _projectTests d]--instance FromJSON ProjectDescription where- parseJSON = withObject "project description" $ \v -> ProjectDescription <$>- v .:: "version" <*>- v .:: "library" <*>- v .:: "executables" <*>- v .:: "tests"--instance Paths ProjectDescription where- paths f (ProjectDescription v lib exes tests) = ProjectDescription v <$> traverse (paths f) lib <*> traverse (paths f) exes <*> traverse (paths f) tests--class Target a where- buildInfo :: a -> Info---- | Library in project-data Library = Library {- _libraryModules :: [[String]],- _libraryBuildInfo :: Info }- deriving (Eq, Read)--instance Target Library where- buildInfo = _libraryBuildInfo--instance Show Library where- show l = unlines $- ["library", "\tmodules:"] ++- (map (tab 2 . intercalate ".") $ _libraryModules l) ++- (map (tab 1) . lines . show $ _libraryBuildInfo l)--instance ToJSON Library where- toJSON l = object [- "modules" .= fmap (intercalate ".") (_libraryModules l),- "info" .= _libraryBuildInfo l]--instance FromJSON Library where- parseJSON = withObject "library" $ \v -> Library <$> (fmap splitModule <$> v .:: "modules") <*> v .:: "info" where- splitModule :: String -> [String]- splitModule = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break (== '.'))--instance Paths Library where- paths f (Library ms info) = Library ms <$> paths f info---- | Executable-data Executable = Executable {- _executableName :: String,- _executablePath :: FilePath,- _executableBuildInfo :: Info }- deriving (Eq, Read)--instance Target Executable where- buildInfo = _executableBuildInfo--instance Show Executable where- show e = unlines $- ["executable " ++ _executableName e, "\tpath: " ++ _executablePath e] ++- (map (tab 1) . lines . show $ _executableBuildInfo e)--instance ToJSON Executable where- toJSON e = object [- "name" .= _executableName e,- "path" .= _executablePath e,- "info" .= _executableBuildInfo e]--instance FromJSON Executable where- parseJSON = withObject "executable" $ \v -> Executable <$>- v .:: "name" <*>- v .:: "path" <*>- v .:: "info"--instance Paths Executable where- paths f (Executable n p info) = Executable n <$> f p <*> paths f info---- | Test-data Test = Test {- _testName :: String,- _testEnabled :: Bool,- _testBuildInfo :: Info }- deriving (Eq, Read)--instance Target Test where- buildInfo = _testBuildInfo--instance Show Test where- show t = unlines $- ["test " ++ _testName t, "\tenabled: " ++ show (_testEnabled t)] ++- (map (tab 1) . lines . show $ _testBuildInfo t)--instance ToJSON Test where- toJSON t = object [- "name" .= _testName t,- "enabled" .= _testEnabled t,- "info" .= _testBuildInfo t]--instance FromJSON Test where- parseJSON = withObject "test" $ \v -> Test <$>- v .:: "name" <*>- v .:: "enabled" <*>- v .:: "info"--instance Paths Test where- paths f (Test n e info) = Test n e <$> paths f info---- | Build info-data Info = Info {- _infoDepends :: [String],- _infoLanguage :: Maybe Language,- _infoExtensions :: [Extension],- _infoGHCOptions :: [String],- _infoSourceDirs :: [FilePath] }- deriving (Eq, Read)--instance Monoid Info where- mempty = Info [] Nothing [] [] []- mappend l r = Info- (ordNub $ _infoDepends l ++ _infoDepends r)- (getFirst $ First (_infoLanguage l) `mappend` First (_infoLanguage r))- (_infoExtensions l ++ _infoExtensions r)- (_infoGHCOptions l ++ _infoGHCOptions r)- (ordNub $ _infoSourceDirs l ++ _infoSourceDirs r)--instance Ord Info where- compare l r = compare (_infoSourceDirs l, _infoDepends l, _infoGHCOptions l) (_infoSourceDirs r, _infoDepends r, _infoGHCOptions r)--instance Show Info where- show i = unlines $ lang ++ exts ++ opts ++ sources where- lang = maybe [] (\l -> ["default-language: " ++ display l]) $ _infoLanguage i- exts- | null (_infoExtensions i) = []- | otherwise = "extensions:" : map (tab 1 . display) (_infoExtensions i)- opts- | null (_infoGHCOptions i) = []- | otherwise = "ghc-options:" : map (tab 1) (_infoGHCOptions i)- sources = "source-dirs:" : (map (tab 1) $ _infoSourceDirs i)--instance ToJSON Info where- toJSON i = object [- "build-depends" .= _infoDepends i,- "language" .= fmap display (_infoLanguage i),- "extensions" .= map display (_infoExtensions i),- "ghc-options" .= _infoGHCOptions i,- "source-dirs" .= _infoSourceDirs i]--instance FromJSON Info where- parseJSON = withObject "info" $ \v -> Info <$>- v .: "build-depends" <*>- ((v .:: "language") >>= traverse (parseDT "Language")) <*>- ((v .:: "extensions") >>= traverse (parseDT "Extension")) <*>- v .:: "ghc-options" <*>- v .:: "source-dirs"- where- parseDT :: Distribution.Text.Text a => String -> String -> Parser a- parseDT typeName v = maybe err return (simpleParse v) where- err = fail $ "Can't parse {}: {}" ~~ typeName ~~ v--instance Paths Info where- paths f (Info deps lang exts opts dirs) = Info deps lang exts opts <$> traverse f dirs---- | Entity with project extensions-data Extensions a = Extensions {- _extensions :: [Extension],- _ghcOptions :: [String],- _entity :: a }- deriving (Eq, Read, Show)--instance Ord a => Ord (Extensions a) where- compare = comparing _entity--instance Functor Extensions where- fmap f (Extensions e o x) = Extensions e o (f x)--instance Applicative Extensions where- pure = Extensions [] []- (Extensions l lo f) <*> (Extensions r ro x) = Extensions (ordNub $ l ++ r) (ordNub $ lo ++ ro) (f x)--instance Foldable Extensions where- foldMap f (Extensions _ _ x) = f x--instance Traversable Extensions where- traverse f (Extensions e o x) = Extensions e o <$> f x--makeLenses ''Project-makeLenses ''ProjectDescription-makeLenses ''Library-makeLenses ''Executable-makeLenses ''Test-makeLenses ''Info-makeLenses ''Extensions+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Project.Types ( + Project(..), projectName, projectPath, projectCabal, projectDescription, project, + ProjectDescription(..), projectVersion, projectLibrary, projectExecutables, projectTests, infos, targetInfos, + Target(..), TargetInfo(..), targetInfoName, targetBuildInfo, targetInfoMain, targetInfoModules, targetInfo, + Library(..), libraryModules, libraryBuildInfo, + Executable(..), executableName, executablePath, executableBuildInfo, + Test(..), testName, testEnabled, testBuildInfo, testMain, + Info(..), infoDepends, infoLanguage, infoExtensions, infoGHCOptions, infoSourceDirs, infoOtherModules, + Extensions(..), extensions, ghcOptions, entity, + ) where + +import Control.DeepSeq (NFData(..)) +import Control.Lens hiding ((.=), (<.>)) +import Data.Aeson +import Data.Maybe +import Data.Monoid +import Data.Ord +import Data.Text (Text) +import qualified Data.Text as T +import Distribution.Text (display) +import Language.Haskell.Extension +import System.FilePath + +import System.Directory.Paths +import HsDev.Util + +-- | Cabal project +data Project = Project { + _projectName :: Text, + _projectPath :: Path, + _projectCabal :: Path, + _projectDescription :: Maybe ProjectDescription } + deriving (Read) + +instance NFData Project where + rnf (Project n p c _) = rnf n `seq` rnf p `seq` rnf c + +instance Eq Project where + l == r = _projectCabal l == _projectCabal r + +instance Ord Project where + compare l r = compare (_projectName l, _projectCabal l) (_projectName r, _projectCabal r) + +instance Show Project where + show p = unlines $ [ + "project " ++ _projectName p ^. path, + "\tcabal: " ++ _projectCabal p ^. path, + "\tdescription:"] ++ concatMap (map (tab 2) . lines . show) (maybeToList $ _projectDescription p) + +instance ToJSON Project where + toJSON p = object [ + "name" .= _projectName p, + "path" .= _projectPath p, + "cabal" .= _projectCabal p, + "description" .= _projectDescription p] + +instance FromJSON Project where + parseJSON = withObject "project" $ \v -> Project <$> + v .:: "name" <*> + v .:: "path" <*> + v .:: "cabal" <*> + v .:: "description" + +instance Paths Project where + paths f (Project nm p c desc) = Project nm <$> paths f p <*> paths f c <*> traverse (paths f) desc + +-- | Make project by .cabal file +project :: FilePath -> Project +project file = Project { + _projectName = fromFilePath . takeBaseName . takeDirectory $ cabal, + _projectPath = fromFilePath . takeDirectory $ cabal, + _projectCabal = fromFilePath cabal, + _projectDescription = Nothing } + where + file' = dropTrailingPathSeparator $ normalise file + cabal + | takeExtension file' == ".cabal" = file' + | otherwise = file' </> (takeBaseName file' <.> "cabal") + +data ProjectDescription = ProjectDescription { + _projectVersion :: Text, + _projectLibrary :: Maybe Library, + _projectExecutables :: [Executable], + _projectTests :: [Test] } + deriving (Eq, Read) + +-- | Build target infos +infos :: Traversal' ProjectDescription Info +infos f desc = (\lib exes tests -> desc { _projectLibrary = lib, _projectExecutables = exes, _projectTests = tests }) <$> + (_Just . buildInfo) f (_projectLibrary desc) <*> + (each . buildInfo) f (_projectExecutables desc) <*> + (each . buildInfo) f (_projectTests desc) + +-- | Build target infos, more detailed +targetInfos :: ProjectDescription -> [TargetInfo] +targetInfos desc = concat [ + map targetInfo $ maybeToList (_projectLibrary desc), + map targetInfo $ _projectExecutables desc, + map targetInfo $ _projectTests desc] + +instance Show ProjectDescription where + show pd = unlines $ + concatMap (lines . show) (maybeToList (_projectLibrary pd)) ++ + concatMap (lines . show) (_projectExecutables pd) ++ + concatMap (lines . show) (_projectTests pd) + +instance ToJSON ProjectDescription where + toJSON d = object [ + "version" .= _projectVersion d, + "library" .= _projectLibrary d, + "executables" .= _projectExecutables d, + "tests" .= _projectTests d] + +instance FromJSON ProjectDescription where + parseJSON = withObject "project description" $ \v -> ProjectDescription <$> + v .:: "version" <*> + v .:: "library" <*> + v .:: "executables" <*> + v .:: "tests" + +instance Paths ProjectDescription where + paths f (ProjectDescription v lib exes tests) = ProjectDescription v <$> traverse (paths f) lib <*> traverse (paths f) exes <*> traverse (paths f) tests + +class Target a where + targetName :: Traversal' a Text + buildInfo :: Lens' a Info + targetMain :: a -> Maybe Path + targetModules :: a -> [[Text]] + +data TargetInfo = TargetInfo { + _targetInfoName :: Maybe Text, + _targetBuildInfo :: Info, + _targetInfoMain :: Maybe Path, + _targetInfoModules :: [[Text]] } + deriving (Eq, Ord, Show) + +targetInfo :: Target a => a -> TargetInfo +targetInfo t = TargetInfo (t ^? targetName) (t ^. buildInfo) (targetMain t) (targetModules t) + +instance Paths TargetInfo where + paths f (TargetInfo n i mp ms) = TargetInfo n <$> paths f i <*> traverse (paths f) mp <*> pure ms + +-- | Library in project +data Library = Library { + _libraryModules :: [[Text]], + _libraryBuildInfo :: Info } + deriving (Eq, Read) + +instance Show Library where + show l = unlines $ + ["library", "\tmodules:"] ++ + (map (tab 2 . T.unpack . T.intercalate ".") $ _libraryModules l) ++ + (map (tab 1) . lines . show $ _libraryBuildInfo l) + +instance ToJSON Library where + toJSON l = object [ + "modules" .= fmap (T.intercalate ".") (_libraryModules l), + "info" .= _libraryBuildInfo l] + +instance FromJSON Library where + parseJSON = withObject "library" $ \v -> Library <$> (fmap (T.split (== '.')) <$> v .:: "modules") <*> v .:: "info" where + +instance Paths Library where + paths f (Library ms info) = Library ms <$> paths f info + +-- | Executable +data Executable = Executable { + _executableName :: Text, + _executablePath :: Path, + _executableBuildInfo :: Info } + deriving (Eq, Read) + +instance Show Executable where + show e = unlines $ + ["executable " ++ T.unpack (_executableName e), "\tpath: " ++ (_executablePath e ^. path)] ++ + (map (tab 1) . lines . show $ _executableBuildInfo e) + +instance ToJSON Executable where + toJSON e = object [ + "name" .= _executableName e, + "path" .= _executablePath e, + "info" .= _executableBuildInfo e] + +instance FromJSON Executable where + parseJSON = withObject "executable" $ \v -> Executable <$> + v .:: "name" <*> + v .:: "path" <*> + v .:: "info" + +instance Paths Executable where + paths f (Executable n p info) = Executable n <$> paths f p <*> paths f info + +-- | Test +data Test = Test { + _testName :: Text, + _testEnabled :: Bool, + _testMain :: Maybe Path, + _testBuildInfo :: Info } + deriving (Eq, Read) + +instance Show Test where + show t = unlines $ + ["test " ++ T.unpack (_testName t), "\tenabled: " ++ show (_testEnabled t)] ++ + maybe [] (\f -> ["\tmain-is: " ++ f ^. path]) (_testMain t) ++ + (map (tab 1) . lines . show $ _testBuildInfo t) + +instance ToJSON Test where + toJSON t = object [ + "name" .= _testName t, + "enabled" .= _testEnabled t, + "main" .= _testMain t, + "info" .= _testBuildInfo t] + +instance FromJSON Test where + parseJSON = withObject "test" $ \v -> Test <$> + v .:: "name" <*> + v .:: "enabled" <*> + v .::? "main" <*> + v .:: "info" + +instance Paths Test where + paths f (Test n e m info) = Test n e <$> traverse (paths f) m <*> paths f info + +-- | Build info +data Info = Info { + _infoDepends :: [Text], + _infoLanguage :: Maybe Language, + _infoExtensions :: [Extension], + _infoGHCOptions :: [Text], + _infoSourceDirs :: [Path], + _infoOtherModules :: [[Text]] } + deriving (Eq, Read) + +instance Monoid Info where + mempty = Info [] Nothing [] [] [] [] + mappend l r = Info + (ordNub $ _infoDepends l ++ _infoDepends r) + (getFirst $ First (_infoLanguage l) `mappend` First (_infoLanguage r)) + (_infoExtensions l ++ _infoExtensions r) + (_infoGHCOptions l ++ _infoGHCOptions r) + (ordNub $ _infoSourceDirs l ++ _infoSourceDirs r) + (ordNub $ _infoOtherModules l ++ _infoOtherModules r) + +instance Ord Info where + compare l r = compare (_infoSourceDirs l, _infoDepends l, _infoGHCOptions l) (_infoSourceDirs r, _infoDepends r, _infoGHCOptions r) + +instance Show Info where + show i = unlines $ lang ++ exts ++ opts ++ sources ++ otherMods where + lang = maybe [] (\l -> ["default-language: " ++ display l]) $ _infoLanguage i + exts + | null (_infoExtensions i) = [] + | otherwise = "extensions:" : map (tab 1 . display) (_infoExtensions i) + opts + | null (_infoGHCOptions i) = [] + | otherwise = "ghc-options:" : map (tab 1 . T.unpack) (_infoGHCOptions i) + sources = "source-dirs:" : map (tab 1 . T.unpack) (_infoSourceDirs i) + otherMods = "other-modules:" : (map (tab 1 . T.unpack) . fmap (T.intercalate ".") $ _infoOtherModules i) + +instance ToJSON Info where + toJSON i = object [ + "build-depends" .= _infoDepends i, + "language" .= _infoLanguage i, + "extensions" .= _infoExtensions i, + "ghc-options" .= _infoGHCOptions i, + "source-dirs" .= _infoSourceDirs i, + "other-modules" .= _infoOtherModules i] + +instance FromJSON Info where + parseJSON = withObject "info" $ \v -> Info <$> + v .: "build-depends" <*> + v .:: "language" <*> + v .:: "extensions" <*> + v .:: "ghc-options" <*> + v .:: "source-dirs" <*> + v .:: "other-modules" + +instance ToJSON Language where + toJSON = toJSON . display + +instance FromJSON Language where + parseJSON = withText "language" $ \txt -> parseDT "Language" (T.unpack txt) + +instance ToJSON Extension where + toJSON = toJSON . display + +instance FromJSON Extension where + parseJSON = withText "extension" $ \txt -> parseDT "Extension" (T.unpack txt) + +instance Paths Info where + paths f (Info deps lang exts opts dirs omods) = Info deps lang exts opts <$> traverse (paths f) dirs <*> pure omods + +-- | Entity with project extensions +data Extensions a = Extensions { + _extensions :: [Extension], + _ghcOptions :: [Text], + _entity :: a } + deriving (Eq, Read, Show) + +instance Ord a => Ord (Extensions a) where + compare = comparing _entity + +instance Functor Extensions where + fmap f (Extensions e o x) = Extensions e o (f x) + +instance Applicative Extensions where + pure = Extensions [] [] + (Extensions l lo f) <*> (Extensions r ro x) = Extensions (ordNub $ l ++ r) (ordNub $ lo ++ ro) (f x) + +instance Foldable Extensions where + foldMap f (Extensions _ _ x) = f x + +instance Traversable Extensions where + traverse f (Extensions e o x) = Extensions e o <$> f x + +makeLenses ''Project +makeLenses ''ProjectDescription +makeLenses ''TargetInfo +makeLenses ''Library +makeLenses ''Executable +makeLenses ''Test +makeLenses ''Info +makeLenses ''Extensions + +instance Target Library where + targetName _ = pure + buildInfo = libraryBuildInfo + targetMain _ = Nothing + targetModules lib' = lib' ^.. libraryModules . each + +instance Target Executable where + targetName = executableName + buildInfo = executableBuildInfo + targetMain exe' = Just $ exe' ^. executablePath + targetModules _ = [] + +instance Target Test where + targetName = testName + buildInfo = testBuildInfo + targetMain test' = fmap toPath (test' ^? testMain . _Just . path) where + toPath f + | haskellSource f = fromFilePath f + | otherwise = fromFilePath (f <.> "hs") + targetModules _ = [] + +instance Target TargetInfo where + targetName = targetInfoName . _Just + buildInfo = targetBuildInfo + targetMain = _targetInfoMain + targetModules = _targetInfoModules
src/HsDev/Sandbox.hs view
@@ -1,161 +1,185 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}--module HsDev.Sandbox (- SandboxType(..), Sandbox(..), sandboxType, sandbox,- isSandbox, guessSandboxType, sandboxFromPath,- findSandbox, searchSandbox, projectSandbox, sandboxPackageDbStack, searchPackageDbStack, restorePackageDbStack,-- -- * cabal-sandbox util- cabalSandboxLib, cabalSandboxPackageDb,-- getModuleOpts- ) where--import Control.Applicative-import Control.Arrow-import Control.DeepSeq (NFData(..))-import Control.Monad.Trans.Maybe-import Control.Monad.Except-import Control.Lens (view, makeLenses)-import Data.Aeson-import Data.List (find)-import Data.Maybe (isJust, fromMaybe)-import qualified Data.Text as T (unpack)-import Distribution.Compiler-import Distribution.System-import qualified Distribution.Text as T (display)-import System.FilePath-import System.Directory-import System.Log.Simple (MonadLog(..))--import System.Directory.Paths-import HsDev.PackageDb-import HsDev.Scan.Browse (withPackages, browsePackages)-import HsDev.Stack-import HsDev.Symbols (moduleOpts)-import HsDev.Symbols.Types (Module(..), ModuleLocation(..), moduleLocation)-import HsDev.Tools.Ghc.Compat as Compat-import HsDev.Util (searchPath, directoryContents, cabalFile)--import qualified Packages as GHC--data SandboxType = CabalSandbox | StackWork deriving (Eq, Ord, Read, Show, Enum, Bounded)--data Sandbox = Sandbox { _sandboxType :: SandboxType, _sandbox :: FilePath } deriving (Eq, Ord)--makeLenses ''Sandbox--instance NFData SandboxType where- rnf CabalSandbox = ()- rnf StackWork = ()--instance NFData Sandbox where- rnf (Sandbox t p) = rnf t `seq` rnf p--instance Show Sandbox where- show (Sandbox _ p) = p--instance ToJSON Sandbox where- toJSON (Sandbox _ p) = toJSON p--instance FromJSON Sandbox where- parseJSON = withText "sandbox" sandboxPath where- sandboxPath = maybe (fail "Not a sandbox") return . sandboxFromPath . T.unpack--instance Paths Sandbox where- paths f (Sandbox st p) = Sandbox st <$> f p--isSandbox :: FilePath -> Bool-isSandbox = isJust . guessSandboxType--guessSandboxType :: FilePath -> Maybe SandboxType-guessSandboxType fpath- | takeFileName fpath == ".cabal-sandbox" = Just CabalSandbox- | takeFileName fpath == ".stack-work" = Just StackWork- | otherwise = Nothing--sandboxFromPath :: FilePath -> Maybe Sandbox-sandboxFromPath fpath = Sandbox <$> guessSandboxType fpath <*> pure fpath---- | Find sandbox in path-findSandbox :: FilePath -> IO (Maybe Sandbox)-findSandbox fpath = do- fpath' <- canonicalize fpath- isDir <- doesDirectoryExist fpath'- if isDir- then do- dirs <- liftM (fpath' :) $ directoryContents fpath'- return $ msum $ map sandboxFromDir dirs- else return Nothing- where- sandboxFromDir :: FilePath -> Maybe Sandbox- sandboxFromDir fdir- | takeFileName fdir == "stack.yaml" = sandboxFromPath (takeDirectory fdir </> ".stack-work")- | otherwise = sandboxFromPath fdir---- | Search sandbox by parent directory-searchSandbox :: FilePath -> IO (Maybe Sandbox)-searchSandbox p = runMaybeT $ searchPath p (MaybeT . findSandbox)---- | Get project sandbox: search up for .cabal, then search for stack.yaml in current directory and cabal sandbox in current + parents-projectSandbox :: FilePath -> IO (Maybe Sandbox)-projectSandbox fpath = runMaybeT $ do- p <- searchPath fpath (MaybeT . getCabalFile)- MaybeT (findSandbox p) <|> searchPath p (MaybeT . findSbox')- where- getCabalFile = directoryContents >=> return . find cabalFile- findSbox' = directoryContents >=> return . msum . map sandboxFromPath---- | Get package-db stack for sandbox-sandboxPackageDbStack :: MonadLog m => Sandbox -> m PackageDbStack-sandboxPackageDbStack (Sandbox CabalSandbox fpath) = do- dir <- cabalSandboxPackageDb- return $ PackageDbStack [PackageDb $ fpath </> dir]-sandboxPackageDbStack (Sandbox StackWork fpath) = liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory fpath---- | Search package-db stack with user-db as default-searchPackageDbStack :: MonadLog m => FilePath -> m PackageDbStack-searchPackageDbStack p = do- mbox <- liftIO $ projectSandbox p- case mbox of- Nothing -> return userDb- Just sbox -> sandboxPackageDbStack sbox---- | Restore package-db stack by package-db-restorePackageDbStack :: MonadLog m => PackageDb -> m PackageDbStack-restorePackageDbStack GlobalDb = return globalDb-restorePackageDbStack UserDb = return userDb-restorePackageDbStack (PackageDb p) = liftM (fromMaybe $ fromPackageDb p) $ runMaybeT $ do- sbox <- MaybeT $ liftIO $ searchSandbox p- lift $ sandboxPackageDbStack sbox---- | Get actual sandbox build path: <arch>-<platform>-<compiler>-<version>-cabalSandboxLib :: MonadLog m => m FilePath-cabalSandboxLib = do- res <- withPackages ["-no-user-package-db"] $- return .- map (GHC.packageNameString &&& GHC.packageVersion) .- fromMaybe [] .- Compat.pkgDatabase- let- compiler = T.display buildCompilerFlavor- CompilerId _ version = buildCompilerId- ver = maybe (T.display version) T.display $ lookup compiler res- return $ T.display buildPlatform ++ "-" ++ compiler ++ "-" ++ ver---- | Get sandbox package-db: <arch>-<platform>-<compiler>-<version>-packages.conf.d-cabalSandboxPackageDb :: MonadLog m => m FilePath-cabalSandboxPackageDb = liftM (++ "-packages.conf.d") cabalSandboxLib---- | Options for GHC for module and project-getModuleOpts :: MonadLog m => [String] -> Module -> m [String]-getModuleOpts opts m = do- pdbs <- case view moduleLocation m of- FileModule fpath _ -> searchPackageDbStack fpath- InstalledModule pdb _ _ -> restorePackageDbStack pdb- ModuleSource _ -> return userDb- pkgs <- browsePackages opts pdbs- return $ concat [- packageDbStackOpts pdbs,- moduleOpts pkgs m,- opts]+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} + +module HsDev.Sandbox ( + SandboxType(..), Sandbox(..), sandboxType, sandbox, + isSandbox, guessSandboxType, sandboxFromPath, + findSandbox, searchSandbox, projectSandbox, sandboxPackageDbStack, searchPackageDbStack, restorePackageDbStack, + + -- * cabal-sandbox util + cabalSandboxLib, cabalSandboxPackageDb, + + getModuleOpts, getProjectTargetOpts, + + getProjectSandbox, + getProjectPackageDbStack + ) where + +import Control.Applicative +import Control.Arrow +import Control.DeepSeq (NFData(..)) +import Control.Monad +import Control.Monad.Trans.Maybe +import Control.Monad.Except +import Control.Lens (view, makeLenses) +import Data.Aeson +import Data.List (find) +import Data.Maybe (isJust, fromMaybe) +import qualified Data.Text as T (unpack) +import Distribution.Compiler +import Distribution.System +import qualified Distribution.Text as T (display) +import System.FilePath +import System.Log.Simple (MonadLog(..)) + +import System.Directory.Paths +import HsDev.PackageDb +import HsDev.Project.Types +import HsDev.Scan.Browse (browsePackages) +import HsDev.Stack hiding (path) +import HsDev.Symbols (moduleOpts, projectTargetOpts) +import HsDev.Symbols.Types (moduleId, Module(..), ModuleLocation(..), moduleLocation) +import HsDev.Tools.Ghc.Worker (GhcM, tmpSession) +import HsDev.Tools.Ghc.Compat as Compat +import HsDev.Util (searchPath, directoryContents, cabalFile) + +import qualified GHC +import qualified Packages as GHC + +data SandboxType = CabalSandbox | StackWork deriving (Eq, Ord, Read, Show, Enum, Bounded) + +data Sandbox = Sandbox { _sandboxType :: SandboxType, _sandbox :: Path } deriving (Eq, Ord) + +makeLenses ''Sandbox + +instance NFData SandboxType where + rnf CabalSandbox = () + rnf StackWork = () + +instance NFData Sandbox where + rnf (Sandbox t p) = rnf t `seq` rnf p + +instance Show Sandbox where + show (Sandbox _ p) = T.unpack p + +instance ToJSON Sandbox where + toJSON (Sandbox _ p) = toJSON p + +instance FromJSON Sandbox where + parseJSON = withText "sandbox" sandboxPath where + sandboxPath = maybe (fail "Not a sandbox") return . sandboxFromPath + +instance Paths Sandbox where + paths f (Sandbox st p) = Sandbox st <$> paths f p + +isSandbox :: Path -> Bool +isSandbox = isJust . guessSandboxType + +guessSandboxType :: Path -> Maybe SandboxType +guessSandboxType fpath + | takeFileName (view path fpath) == ".cabal-sandbox" = Just CabalSandbox + | takeFileName (view path fpath) == ".stack-work" = Just StackWork + | otherwise = Nothing + +sandboxFromPath :: Path -> Maybe Sandbox +sandboxFromPath fpath = Sandbox <$> guessSandboxType fpath <*> pure fpath + +-- | Find sandbox in path +findSandbox :: Path -> IO (Maybe Sandbox) +findSandbox fpath = do + fpath' <- canonicalize fpath + isDir <- dirExists fpath' + if isDir + then do + dirs <- liftM ((fpath' :) . map fromFilePath) $ directoryContents (view path fpath') + return $ msum $ map sandboxFromDir dirs + else return Nothing + where + sandboxFromDir :: Path -> Maybe Sandbox + sandboxFromDir fdir + | takeFileName (view path fdir) == "stack.yaml" = sandboxFromPath (fromFilePath (takeDirectory (view path fdir) </> ".stack-work")) + | otherwise = sandboxFromPath fdir + +-- | Search sandbox by parent directory +searchSandbox :: Path -> IO (Maybe Sandbox) +searchSandbox p = runMaybeT $ searchPath (view path p) (MaybeT . findSandbox . fromFilePath) + +-- | Get project sandbox: search up for .cabal, then search for stack.yaml in current directory and cabal sandbox in current + parents +projectSandbox :: Path -> IO (Maybe Sandbox) +projectSandbox fpath = runMaybeT $ do + p <- searchPath (view path fpath) (MaybeT . getCabalFile) + MaybeT (findSandbox $ fromFilePath p) <|> searchPath p (MaybeT . findSbox') + where + getCabalFile = directoryContents >=> return . find cabalFile + findSbox' = directoryContents >=> return . msum . map (sandboxFromPath . fromFilePath) + +-- | Get package-db stack for sandbox +sandboxPackageDbStack :: Sandbox -> GhcM PackageDbStack +sandboxPackageDbStack (Sandbox CabalSandbox fpath) = do + dir <- cabalSandboxPackageDb + return $ PackageDbStack [PackageDb $ fromFilePath $ view path fpath </> dir] +sandboxPackageDbStack (Sandbox StackWork fpath) = liftM (view stackPackageDbStack) $ projectEnv $ takeDirectory (view path fpath) + +-- | Search package-db stack with user-db as default +searchPackageDbStack :: Path -> GhcM PackageDbStack +searchPackageDbStack p = do + mbox <- liftIO $ projectSandbox p + case mbox of + Nothing -> return userDb + Just sbox -> sandboxPackageDbStack sbox + +-- | Restore package-db stack by package-db +restorePackageDbStack :: PackageDb -> GhcM PackageDbStack +restorePackageDbStack GlobalDb = return globalDb +restorePackageDbStack UserDb = return userDb +restorePackageDbStack (PackageDb p) = liftM (fromMaybe $ fromPackageDbs [p]) $ runMaybeT $ do + sbox <- MaybeT $ liftIO $ searchSandbox p + lift $ sandboxPackageDbStack sbox + +-- | Get actual sandbox build path: <arch>-<platform>-<compiler>-<version> +cabalSandboxLib :: GhcM FilePath +cabalSandboxLib = do + tmpSession ["-no-user-package-db"] + df <- GHC.getSessionDynFlags + let + res = + map (GHC.packageNameString &&& GHC.packageVersion) . + fromMaybe [] . Compat.pkgDatabase $ df + compiler = T.display buildCompilerFlavor + CompilerId _ version = buildCompilerId + ver = maybe (T.display version) T.display $ lookup compiler res + return $ T.display buildPlatform ++ "-" ++ compiler ++ "-" ++ ver + +-- | Get sandbox package-db: <arch>-<platform>-<compiler>-<version>-packages.conf.d +cabalSandboxPackageDb :: GhcM FilePath +cabalSandboxPackageDb = liftM (++ "-packages.conf.d") cabalSandboxLib + +-- | Options for GHC for module and project +getModuleOpts :: [String] -> Module -> GhcM [String] +getModuleOpts opts m = do + pdbs <- case view (moduleId . moduleLocation) m of + FileModule fpath _ -> searchPackageDbStack fpath + InstalledModule _ _ _ -> return userDb + _ -> return userDb + pkgs <- browsePackages opts pdbs + return $ concat [ + packageDbStackOpts pdbs, + moduleOpts pkgs m, + opts] + +-- | Options for GHC for project target +getProjectTargetOpts :: [String] -> Project -> Info -> GhcM [String] +getProjectTargetOpts opts proj t = do + pdbs <- searchPackageDbStack $ view projectPath proj + pkgs <- browsePackages opts pdbs + return $ concat [ + packageDbStackOpts pdbs, + projectTargetOpts pkgs proj t, + opts] + +-- | Get sandbox of project (if any) +getProjectSandbox :: MonadLog m => Project -> m (Maybe Sandbox) +getProjectSandbox = liftIO . projectSandbox . view projectPath + +-- | Get project package-db stack +getProjectPackageDbStack :: Project -> GhcM PackageDbStack +getProjectPackageDbStack = getProjectSandbox >=> maybe (return userDb) sandboxPackageDbStack
src/HsDev/Scan.hs view
@@ -1,209 +1,251 @@-{-# LANGUAGE FlexibleInstances #-}--module HsDev.Scan (- -- * Enumerate functions- CompileFlag, ModuleToScan, ProjectToScan, PackageDbToScan, ScanContents(..),- EnumContents(..),- enumProject, enumSandbox, enumDirectory,-- -- * Scan- scanProjectFile,- scanModule, scanModify, upToDate, rescanModule, changedModule, changedModules,-- -- * Reexportss- module HsDev.Database,- module HsDev.Symbols.Types,- module Control.Monad.Except,- ) where--import Control.DeepSeq-import Control.Lens (view, preview, set, over, each, _Right, _1, _2, _3, (^.), (^..))-import Control.Monad.Except-import Data.Maybe (catMaybes, fromMaybe, isJust, listToMaybe)-import Data.List (intercalate)-import System.Directory-import Text.Format--import HsDev.Error-import HsDev.Scan.Browse (browsePackages, browseModules)-import HsDev.Server.Types (FileSource(..), CommandMonad(..))-import HsDev.Sandbox-import HsDev.Symbols-import HsDev.Symbols.Types-import HsDev.Database-import HsDev.Display-import HsDev.Inspect-import HsDev.Util---- | Compile flags-type CompileFlag = String--- | Module with flags ready to scan-type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe String)--- | Project ready to scan-type ProjectToScan = (Project, [ModuleToScan])--- | Package-db sandbox to scan (top of stack)-type PackageDbToScan = PackageDbStack---- | Scan info-data ScanContents = ScanContents {- modulesToScan :: [ModuleToScan],- projectsToScan :: [ProjectToScan],- sandboxesToScan :: [PackageDbStack] }--instance NFData ScanContents where- rnf (ScanContents ms ps ss) = rnf ms `seq` rnf ps `seq` rnf ss--instance Monoid ScanContents where- mempty = ScanContents [] [] []- mappend (ScanContents lm lp ls) (ScanContents rm rp rs) = ScanContents- (uniqueBy (view _1) $ lm ++ rm)- (uniqueBy (view _1) $ lp ++ rp)- (ordNub $ ls ++ rs)--instance Formattable ScanContents where- formattable (ScanContents ms ps cs) = formattable str where- str :: String- str = format "modules: {}, projects: {}, package-dbs: {}"- ~~ (intercalate ", " $ ms ^.. each . _1 . moduleFile)- ~~ (intercalate ", " $ ps ^.. each . _1 . projectPath)- ~~ (intercalate ", " $ map (display . topPackageDb) $ cs ^.. each)--class EnumContents a where- enumContents :: CommandMonad m => a -> m ScanContents--instance EnumContents ModuleLocation where- enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] []--instance EnumContents (Extensions ModuleLocation) where- enumContents ex = return $ ScanContents [(view entity ex, extensionsOpts ex, Nothing)] [] []--instance EnumContents Project where- enumContents = enumProject--instance EnumContents PackageDbStack where- enumContents pdbs = return $ ScanContents [] [] (packageDbStacks pdbs)--instance EnumContents Sandbox where- enumContents = enumSandbox--instance {-# OVERLAPPING #-} EnumContents a => EnumContents [a] where- enumContents = liftM mconcat . tries . map enumContents--instance {-# OVERLAPS #-} EnumContents FilePath where- enumContents f- | haskellSource f = hsdevLiftIO $ do- mproj <- liftIO $ locateProject f- case mproj of- Nothing -> enumContents $ FileModule f Nothing- Just proj -> do- ScanContents _ [(_, mods)] _ <- enumContents proj- return $ ScanContents (filter ((== Just f) . preview (_1 . moduleFile)) mods) [] []- | otherwise = enumDirectory f--instance EnumContents FileSource where- enumContents (FileSource f mcts)- | haskellSource f = do- ScanContents [(m, opts, _)] _ _ <- enumContents f- return $ ScanContents [(m, opts, mcts)] [] []- | otherwise = return mempty---- | Enum project sources-enumProject :: CommandMonad m => Project -> m ScanContents-enumProject p = hsdevLiftIO $ do- p' <- liftIO $ loadProject p- pdbs <- searchPackageDbStack (view projectPath p')- pkgs <- liftM (map $ view (package . packageName)) $ browsePackages [] pdbs- let- projOpts :: FilePath -> [String]- projOpts f = concatMap makeOpts $ fileTargets p' f where- makeOpts :: Info -> [String]- makeOpts i = concat [- ["-hide-all-packages"],- ["-package " ++ view projectName p'],- ["-package " ++ dep | dep <- view infoDepends i, dep `elem` pkgs]]- srcs <- liftIO $ projectSources p'- let- mlocs = over each (\src -> over ghcOptions (++ projOpts (view entity src)) . over entity (\f -> FileModule f (Just p')) $ src) srcs- mods <- liftM modulesToScan $ enumContents mlocs- return $ ScanContents [] [(p', mods)] [] -- (sandboxCabals sboxes)---- | Enum sandbox-enumSandbox :: CommandMonad m => Sandbox -> m ScanContents-enumSandbox = sandboxPackageDbStack >=> enumContents---- | Enum directory modules-enumDirectory :: CommandMonad m => FilePath -> m ScanContents-enumDirectory dir = hsdevLiftIO $ do- cts <- liftIO $ traverseDirectory dir- let- projects = filter cabalFile cts- sources = filter haskellSource cts- dirs <- liftIO $ filterM doesDirectoryExist cts- sboxes <- liftM catMaybes $ triesMap (liftIO . findSandbox) dirs- pdbs <- mapM enumSandbox sboxes- projs <- liftM mconcat $ triesMap (enumProject . project) projects- let- projPaths = map (view projectPath . fst) $ projectsToScan projs- standalone = map (`FileModule` Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) sources- return $ mconcat [- ScanContents [(s, [], Nothing) | s <- standalone] [] [],- projs,- mconcat pdbs]---- | Scan project file-scanProjectFile :: CommandMonad m => [String] -> FilePath -> m Project-scanProjectFile _ f = hsdevLiftIO $ do- proj <- (liftIO $ locateProject f) >>= maybe (hsdevError $ FileNotFound f) return- liftIO $ loadProject proj---- | Scan module-scanModule :: CommandMonad m => [(String, String)] -> [String] -> ModuleLocation -> Maybe String -> m InspectedModule-scanModule defines opts (FileModule f p) mcts = hsdevLiftIO $ liftM setProj $ liftIO $ inspectFile defines opts f mcts where- setProj =- set (inspectedId . moduleProject) p .- set (inspectionResult . _Right . moduleLocation . moduleProject) p-scanModule _ opts mloc@(InstalledModule c _ n) _ = hsdevLiftIO $ do- pdbs <- getDbs c- ims <- browseModules opts pdbs [mloc]- maybe (hsdevError $ BrowseNoModuleInfo n) return $ listToMaybe ims- where- getDbs :: CommandMonad m => PackageDb -> m PackageDbStack- getDbs = maybe (return userDb) searchPackageDbStack . preview packageDb-scanModule _ _ (ModuleSource _) _ = hsdevError $ InspectError "Can inspect only modules in file or cabal"---- | Scan additional info and modify scanned module-scanModify :: CommandMonad m => ([String] -> PackageDbStack -> Module -> m Module) -> InspectedModule -> m InspectedModule-scanModify f im = traverse f' im where- f' m = do- pdbs <- case view moduleLocation m of- -- TODO: Get actual sandbox stack- FileModule fpath _ -> searchPackageDbStack fpath- InstalledModule pdb _ _ -> maybe (return userDb) searchPackageDbStack $ preview packageDb pdb- _ -> return userDb- f (fromMaybe [] $ preview (inspection . inspectionOpts) im) pdbs m---- | Is inspected module up to date?-upToDate :: [String] -> InspectedModule -> IO Bool-upToDate opts im = case view inspectedId im of- FileModule f _ -> liftM (== view inspection im) $ fileInspection f opts- InstalledModule _ _ _ -> return $ view inspection im == InspectionAt 0 opts- _ -> return False---- | Rescan inspected module-rescanModule :: CommandMonad m => [(String, String)] -> [String] -> InspectedModule -> m (Maybe InspectedModule)-rescanModule defines opts im = do- up <- liftIO $ upToDate opts im- if up- then return Nothing- else fmap Just $ scanModule defines opts (view inspectedId im) Nothing---- | Is module new or recently changed-changedModule :: Database -> [String] -> ModuleLocation -> IO Bool-changedModule db opts m = maybe (return True) (liftM not . liftIO . upToDate opts) m' where- m' = lookupInspected m db---- | Returns new (to scan) and changed (to rescan) modules-changedModules :: Database -> [String] -> [ModuleToScan] -> IO [ModuleToScan]-changedModules db opts = filterM $ \m -> if isJust (m ^. _3)- then return True- else changedModule db (opts ++ (m ^. _2)) (m ^. _1)+{-# LANGUAGE FlexibleInstances, TypeOperators, TypeApplications, OverloadedStrings #-} + +module HsDev.Scan ( + -- * Enumerate functions + CompileFlag, ModuleToScan, ProjectToScan, PackageDbToScan, ScanContents(..), + EnumContents(..), + enumRescan, enumDependent, enumProject, enumSandbox, enumDirectory, + + -- * Scan + scanProjectFile, + scanModify, + upToDate, changedModules, + getFileContents, + + -- * Reexportss + module HsDev.Symbols.Types, + module Control.Monad.Except, + ) where + +import Control.DeepSeq +import Control.Lens +import Control.Monad.Except +import Data.Deps +import Data.Maybe (catMaybes, isJust, listToMaybe) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.List (intercalate) +import Data.Text (Text) +import Data.Text.Lens (unpacked) +import qualified Data.Text as T +import Data.Time.Clock.POSIX (POSIXTime) +import Data.Traversable (for) +import Data.String (IsString, fromString) +import qualified Data.Set as S +import System.Directory +import Text.Format +import qualified System.Log.Simple as Log + +import HsDev.Error +import qualified HsDev.Database.SQLite as SQLite +import HsDev.Database.SQLite.Select +import HsDev.Scan.Browse (browsePackages) +import HsDev.Server.Types (FileSource(..), SessionMonad(..), CommandMonad(..), inSessionGhc) +import HsDev.Sandbox +import HsDev.Symbols +import HsDev.Symbols.Types +import HsDev.Display +import HsDev.Inspect +import HsDev.Util +import System.Directory.Paths + +-- | Compile flags +type CompileFlag = String +-- | Module with flags ready to scan +type ModuleToScan = (ModuleLocation, [CompileFlag], Maybe Text) +-- | Project ready to scan +type ProjectToScan = (Project, [ModuleToScan]) +-- | Package-db sandbox to scan (top of stack) +type PackageDbToScan = PackageDbStack + +-- | Scan info +data ScanContents = ScanContents { + modulesToScan :: [ModuleToScan], + projectsToScan :: [ProjectToScan], + sandboxesToScan :: [PackageDbStack] } + +instance NFData ScanContents where + rnf (ScanContents ms ps ss) = rnf ms `seq` rnf ps `seq` rnf ss + +instance Monoid ScanContents where + mempty = ScanContents [] [] [] + mappend (ScanContents lm lp ls) (ScanContents rm rp rs) = ScanContents + (uniqueBy (view _1) $ lm ++ rm) + (uniqueBy (view _1) $ lp ++ rp) + (ordNub $ ls ++ rs) + +instance Formattable ScanContents where + formattable (ScanContents ms ps cs) = formattable str where + str :: String + str = format "modules: {}, projects: {}, package-dbs: {}" + ~~ (T.intercalate comma $ ms ^.. each . _1 . moduleFile) + ~~ (T.intercalate comma $ ps ^.. each . _1 . projectPath) + ~~ (intercalate comma $ map (display . topPackageDb) $ cs ^.. each) + comma :: IsString s => s + comma = fromString ", " + +class EnumContents a where + enumContents :: CommandMonad m => a -> m ScanContents + +instance EnumContents ModuleLocation where + enumContents mloc = return $ ScanContents [(mloc, [], Nothing)] [] [] + +instance EnumContents (Extensions ModuleLocation) where + enumContents ex = return $ ScanContents [(view entity ex, extensionsOpts ex, Nothing)] [] [] + +instance EnumContents Project where + enumContents = enumProject + +instance EnumContents PackageDbStack where + enumContents pdbs = return $ ScanContents [] [] (packageDbStacks pdbs) + +instance EnumContents Sandbox where + enumContents = enumSandbox + +instance {-# OVERLAPPABLE #-} EnumContents a => EnumContents [a] where + enumContents = liftM mconcat . tries . map enumContents + +instance {-# OVERLAPPING #-} EnumContents FilePath where + enumContents f + | haskellSource f = hsdevLiftIO $ do + mproj <- liftIO $ locateProject f + case mproj of + Nothing -> enumContents $ FileModule (fromFilePath f) Nothing + Just proj -> do + ScanContents _ [(_, mods)] _ <- enumContents proj + return $ ScanContents (filter ((== Just f) . preview (_1 . moduleFile . path)) mods) [] [] + | otherwise = enumDirectory f + +instance {-# OVERLAPPING #-} EnumContents Path where + enumContents = enumContents . view path + +instance EnumContents FileSource where + enumContents (FileSource f mcts) + | haskellSource (view path f) = do + ScanContents [(m, opts, _)] _ _ <- enumContents f + return $ ScanContents [(m, opts, mcts)] [] [] + | otherwise = return mempty + +-- | Enum rescannable (i.e. already scanned) file +enumRescan :: CommandMonad m => FilePath -> m ScanContents +enumRescan fpath = Log.scope "enum-rescan" $ do + ms <- SQLite.query @_ @(ModuleLocation SQLite.:. Inspection) + (toQuery $ qModuleLocation `mappend` select_ ["ml.inspection_time", "ml.inspection_opts"] [] [] `mappend` where_ ["ml.file == ?"]) (SQLite.Only fpath) + case ms of + [] -> do + Log.sendLog Log.Warning $ "file {} not found" ~~ fpath + return mempty + ((mloc SQLite.:. insp):_) -> do + when (length ms > 1) $ Log.sendLog Log.Warning $ "several modules with file == {} found, taking first one" ~~ fpath + return $ ScanContents [(mloc, insp ^.. inspectionOpts . each . unpacked, Nothing)] [] [] + +-- | Enum file dependent +enumDependent :: CommandMonad m => FilePath -> m ScanContents +enumDependent fpath = Log.scope "enum-dependent" $ do + ms <- SQLite.query @_ @ModuleId + (toQuery $ qModuleId `mappend` where_ ["mu.file == ?"]) (SQLite.Only fpath) + case ms of + [] -> do + Log.sendLog Log.Warning $ "file {} not found" ~~ fpath + return mempty + (mid:_) -> do + when (length ms > 1) $ Log.sendLog Log.Warning $ "several modules with file == {} found, taking first one" ~~ fpath + let + mcabal = mid ^? moduleLocation . moduleProject . _Just . projectCabal + depList <- SQLite.query @_ @(Path, Path) "select d.module_file, d.depends_file from sources_depends as d, projects_modules_scope as ps where ps.cabal is ? and ps.module_id == d.module_id;" + (SQLite.Only mcabal) + let + rdeps = inverse . either (const mempty) id . flatten . mconcat . map (uncurry dep) $ depList + dependent = rdeps ^. ix (fromFilePath fpath) + liftM mconcat $ mapM (enumRescan . view path) dependent + +-- | Enum project sources +enumProject :: CommandMonad m => Project -> m ScanContents +enumProject p = hsdevLiftIO $ do + p' <- liftIO $ loadProject p + pdbs <- inSessionGhc $ searchPackageDbStack (view projectPath p') + pkgs <- inSessionGhc $ liftM (S.fromList . map (view (package . packageName))) $ browsePackages [] pdbs + let + projOpts :: Path -> [Text] + projOpts f = map fromString $ concatMap makeOpts $ fileTargets p' f where + makeOpts :: Info -> [String] + makeOpts i = concat [ + ["-hide-all-packages"], + ["-package " ++ view (projectName . path) p'], + ["-package " ++ T.unpack dep' | dep' <- view infoDepends i, dep' `S.member` pkgs]] + srcs <- liftIO $ projectSources p' + let + mlocs = over each (\src -> over ghcOptions (++ projOpts (view entity src)) . over entity (\f -> FileModule f (Just p')) $ src) srcs + mods <- liftM modulesToScan $ enumContents mlocs + return $ ScanContents [] [(p', mods)] [] -- (sandboxCabals sboxes) + +-- | Enum sandbox +enumSandbox :: CommandMonad m => Sandbox -> m ScanContents +enumSandbox = (inSessionGhc . sandboxPackageDbStack) >=> enumContents + +-- | Enum directory modules +enumDirectory :: CommandMonad m => FilePath -> m ScanContents +enumDirectory dir = hsdevLiftIO $ do + cts <- liftIO $ traverseDirectory dir + let + projects = filter cabalFile cts + sources = filter haskellSource cts + dirs <- liftIO $ filterM doesDirectoryExist cts + sboxes <- liftM catMaybes $ triesMap (liftIO . findSandbox . fromFilePath) dirs + pdbs <- mapM enumSandbox sboxes + projs <- liftM mconcat $ triesMap (enumProject . project) projects + let + projPaths = map (view projectPath . fst) $ projectsToScan projs + standalone = map (`FileModule` Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) $ map fromFilePath sources + return $ mconcat [ + ScanContents [(s, [], Nothing) | s <- standalone] [] [], + projs, + mconcat pdbs] + +-- | Scan project file +scanProjectFile :: CommandMonad m => [String] -> Path -> m Project +scanProjectFile _ f = hsdevLiftIO $ do + proj <- (liftIO $ locateProject (view path f)) >>= maybe (hsdevError $ FileNotFound f) return + liftIO $ loadProject proj + +-- | Scan additional info and modify scanned module +scanModify :: CommandMonad m => ([String] -> Module -> m Module) -> InspectedModule -> m InspectedModule +scanModify f im = traverse f' im where + f' m = f (toListOf (inspection . inspectionOpts . each . unpacked) im) m + +-- | Is inspected module up to date? +upToDate :: SessionMonad m => ModuleLocation -> [String] -> Inspection -> m Bool +upToDate mloc opts insp = do + insp' <- liftIO $ moduleInspection mloc opts + mfinsp <- fmap join $ for (mloc ^? moduleFile) $ \fpath -> do + tm <- SQLite.query @_ @(SQLite.Only Double) "select mtime from file_contents where file = ?;" (SQLite.Only fpath) + return $ fmap (fileContentsInspection_ opts . fromRational . toRational . SQLite.fromOnly) (listToMaybe tm) + let + lastInsp = maybe insp' (max insp') mfinsp + return $ fresh insp lastInsp + +-- | Returns new (to scan) and changed (to rescan) modules +changedModules :: SessionMonad m => Map ModuleLocation Inspection -> [String] -> [ModuleToScan] -> m [ModuleToScan] +changedModules inspMap opts = filterM $ \m -> if isJust (m ^. _3) + then return True + else maybe + (return True) + (liftM not . upToDate (m ^. _1) (opts ++ (m ^. _2))) + (M.lookup (m ^. _1) inspMap) + +-- | Returns file contents if it was set and still actual +getFileContents :: SessionMonad m => Path -> m (Maybe (POSIXTime, Text)) +getFileContents fpath = do + mcts <- SQLite.query @_ @(Double, Text) "select mtime, contents from file_contents where file = ?;" (SQLite.Only fpath) + insp <- liftIO $ fileInspection fpath [] + case listToMaybe mcts of + Nothing -> return Nothing + Just (tm, cts) -> do + let + tm' = fromRational (toRational tm) + fmtime <- maybe (hsdevError $ OtherError "impossible: inspection time not set after call to `fileInspection`") return $ insp ^? inspectionAt + if fmtime < tm' + then return (Just (tm', cts)) + else do + SQLite.execute "delete from file_contents where file = ?;" (SQLite.Only fpath) + return Nothing
src/HsDev/Scan/Browse.hs view
@@ -1,248 +1,245 @@-module HsDev.Scan.Browse (- -- * List all packages- browsePackages, browsePackagesDeps,- -- * Scan cabal modules- listModules, browseModules, browse, browseDb,- -- * Helpers- withPackages, withPackages_,- readPackage, readPackageConfig, ghcPackageDb, ghcModuleLocation,- packageDbCandidate, packageDbCandidate_,- packageConfigs, packageDbModules, lookupModule_,-- module Control.Monad.Except- ) where--import Control.Arrow-import Control.Lens (view, preview, _Just)-import Control.Monad.Catch (MonadCatch, catch, SomeException)-import Control.Monad.Except-import Data.List (isPrefixOf)-import Data.Maybe-import Data.String (fromString)-import Data.Version-import System.Directory-import System.FilePath-import System.Log.Simple.Monad (MonadLog)--import Data.Deps-import HsDev.PackageDb-import HsDev.Symbols-import HsDev.Error-import HsDev.Tools.Base (inspect)-import HsDev.Tools.Ghc.Worker (GhcM, runGhcM, SessionTarget(..), workerSession)-import HsDev.Tools.Ghc.Compat as Compat-import HsDev.Util (ordNub)--import qualified ConLike as GHC-import qualified DataCon as GHC-import qualified DynFlags as GHC-import qualified GHC-import qualified GHC.PackageDb as GHC-import qualified GhcMonad as GHC (liftIO)-import GhcMonad (GhcMonad)-import qualified GHC.Paths as GHC-import qualified Name as GHC-import qualified Outputable as GHC-import qualified Packages as GHC-import qualified TyCon as GHC-import qualified Type as GHC-import qualified Var as GHC-import qualified Pretty---- | Browse packages-browsePackages :: MonadLog m => [String] -> PackageDbStack -> m [PackageConfig]-browsePackages opts dbs = withPackages_ (packageDbStackOpts dbs ++ opts) $- liftM (map readPackageConfig) packageConfigs---- | Get packages with deps-browsePackagesDeps :: MonadLog m => [String] -> PackageDbStack -> m (Deps PackageConfig)-browsePackagesDeps opts dbs = withPackages (packageDbStackOpts dbs ++ opts) $ \df -> do- cfgs <- packageConfigs- return $ mapDeps (toPkg df) $ mconcat $ map (uncurry deps) $- map (Compat.unitId &&& Compat.depends df) cfgs- where- toPkg df' = readPackageConfig . getPackageDetails df'--listModules :: MonadLog m => [String] -> PackageDbStack -> m [ModuleLocation]-listModules opts dbs = withPackages_ (packageDbStackOpts dbs ++ opts) $ do- ms <- packageDbModules- pdbs <- mapM (liftIO . ghcPackageDb . fst) ms- return [ghcModuleLocation pdb p m | (pdb, (p, m)) <- zip pdbs ms]--browseModules :: MonadLog m => [String] -> PackageDbStack -> [ModuleLocation] -> m [InspectedModule]-browseModules opts dbs mlocs = withPackages_ (packageDbStackOpts dbs ++ opts) $ do- ms <- packageDbModules- pdbs <- mapM (liftIO . ghcPackageDb . fst) ms- liftM catMaybes $ sequence [browseModule' pdb p m | (pdb, (p, m)) <- zip pdbs ms, ghcModuleLocation pdb p m `elem` mlocs]- where- browseModule' :: PackageDb -> GHC.PackageConfig -> GHC.Module -> GhcM (Maybe InspectedModule)- browseModule' pdb p m = tryT $ inspect (ghcModuleLocation pdb p m) (return $ InspectionAt 0 opts) (browseModule pdb p m)---- | Browse modules, if third argument is True - browse only modules in top of package-db stack-browse :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule]-browse opts dbs = listModules opts dbs >>= browseModules opts dbs---- | Browse modules in top of package-db stack-browseDb :: MonadLog m => [String] -> PackageDbStack -> m [InspectedModule]-browseDb opts dbs = listModules opts dbs >>= browseModules opts dbs . filter inTop where- inTop = (== Just (topPackageDb dbs)) . preview modulePackageDb--browseModule :: PackageDb -> GHC.PackageConfig -> GHC.Module -> GhcM Module-browseModule pdb package' m = do- df <- GHC.getSessionDynFlags- mi <- GHC.getModuleInfo m >>= maybe (hsdevError $ BrowseNoModuleInfo thisModule) return- ds <- mapM (toDecl df mi) (GHC.modInfoExports mi)- return Module {- _moduleName = fromString thisModule,- _moduleDocs = Nothing,- _moduleLocation = thisLoc df,- _moduleExports = Just [ExportName Nothing (view declarationName d) ThingNothing | d <- ds],- _moduleImports = [import_ iname | iname <- ordNub (mapMaybe (preview definedModule) ds), iname /= fromString thisModule],- _moduleDeclarations = sortDeclarations ds }- where- thisModule = GHC.moduleNameString (GHC.moduleName m)- thisLoc df = view moduleIdLocation $ mloc df m- mloc df m' = ModuleId (fromString mname') $- ghcModuleLocation pdb (fromMaybe package' $ GHC.lookupPackage df (moduleUnitId m')) m'- where- mname' = GHC.moduleNameString $ GHC.moduleName m'- toDecl df minfo n = do- tyInfo <- GHC.modInfoLookupName minfo n- tyResult <- maybe (inModuleSource n) (return . Just) tyInfo- dflag <- GHC.getSessionDynFlags- let- decl' = decl (fromString $ GHC.getOccString n) $ fromMaybe- (Function Nothing [] Nothing)- (tyResult >>= showResult dflag)- return $ decl' `definedIn` mloc df (GHC.nameModule n)- definedModule = declarationDefined . _Just . moduleIdName- showResult :: GHC.DynFlags -> GHC.TyThing -> Maybe DeclarationInfo- showResult dflags (GHC.AnId i) = Just $ Function (Just $ fromString $ formatType dflags GHC.varType i) [] Nothing- showResult dflags (GHC.AConLike c) = case c of- GHC.RealDataCon d -> Just $ Function (Just $ fromString $ formatType dflags GHC.dataConRepType d) [] Nothing- GHC.PatSynCon p -> Just $ Function (Just $ fromString $ formatType dflags patSynType p) [] Nothing- showResult _ (GHC.ATyCon t) = Just $ tcon $ TypeInfo Nothing (map (fromString . GHC.getOccString) $ GHC.tyConTyVars t) Nothing [] where- tcon- | GHC.isAlgTyCon t && not (GHC.isNewTyCon t) && not (GHC.isClassTyCon t) = Data- | GHC.isNewTyCon t = NewType- | GHC.isClassTyCon t = Class- | GHC.isTypeSynonymTyCon t = Type- | otherwise = Type- showResult _ _ = Nothing--withInitializedPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a-withInitializedPackages ghcOpts cont = runGhcM (Just GHC.libdir) $ do- workerSession $ SessionGhc ghcOpts- fs <- GHC.getSessionDynFlags- cleanupHandler fs $ do- (fs', _, _) <- GHC.parseDynamicFlags fs (map GHC.noLoc ghcOpts)- _ <- GHC.setSessionDynFlags fs'- (result, _) <- GHC.liftIO $ GHC.initPackages fs'- cont result--withPackages :: MonadLog m => [String] -> (GHC.DynFlags -> GhcM a) -> m a-withPackages ghcOpts cont = withInitializedPackages ghcOpts cont--withPackages_ :: MonadLog m => [String] -> GhcM a -> m a-withPackages_ ghcOpts act = withPackages ghcOpts (const act)--inModuleSource :: GhcMonad m => GHC.Name -> m (Maybe GHC.TyThing)-inModuleSource nm = GHC.getModuleInfo (GHC.nameModule nm) >> GHC.lookupGlobalName nm--formatType :: GHC.DynFlags -> (a -> GHC.Type) -> a -> String-formatType dflag f x = showOutputable dflag (removeForAlls $ f x)--removeForAlls :: GHC.Type -> GHC.Type-removeForAlls ty = removeForAlls' ty' tty' where- ty' = GHC.dropForAlls ty- tty' = GHC.splitFunTy_maybe ty'--removeForAlls' :: GHC.Type -> Maybe (GHC.Type, GHC.Type) -> GHC.Type-removeForAlls' ty Nothing = ty-removeForAlls' ty (Just (pre, ftype))- | GHC.isPredTy pre = GHC.mkFunTy pre (GHC.dropForAlls ftype)- | otherwise = ty--showOutputable :: GHC.Outputable a => GHC.DynFlags -> a -> String-showOutputable dflag = unwords . lines . showUnqualifiedPage dflag . GHC.ppr--showUnqualifiedPage :: GHC.DynFlags -> GHC.SDoc -> String-showUnqualifiedPage dflag = renderStyle Pretty.LeftMode 0 . GHC.withPprStyleDoc dflag (Compat.unqualStyle dflag)--tryT :: MonadCatch m => m a -> m (Maybe a)-tryT act = catch (liftM Just act) (const (return Nothing) . (id :: SomeException -> SomeException))--readPackage :: GHC.PackageConfig -> ModulePackage-readPackage pc = ModulePackage (GHC.packageNameString pc) (showVersion (GHC.packageVersion pc))--readPackageConfig :: GHC.PackageConfig -> PackageConfig-readPackageConfig pc = PackageConfig- (readPackage pc)- (map (fromString . GHC.moduleNameString . Compat.exposedModuleName) $ GHC.exposedModules pc)- (GHC.exposed pc)--ghcModuleLocation :: PackageDb -> GHC.PackageConfig -> GHC.Module -> ModuleLocation-ghcModuleLocation pdb p m = InstalledModule pdb (Just $ readPackage p) (GHC.moduleNameString $ GHC.moduleName m)--ghcPackageDb :: GHC.PackageConfig -> IO PackageDb-ghcPackageDb = maybe (return GlobalDb) packageDbCandidate_ . listToMaybe . GHC.libraryDirs---- | Get package-db for package library directory--- Haskish way--- global-db - library is in <path>/lib/<package> and there exists <path>/lib/package.conf.d--- user-db - library is in cabal user directory--- package-db--- cabal-sandbox - library in .../.cabal-sandbox/.../<platform-ghc-ver>/<package>--- then package-db is .../.cabal-sandbox/<platform-ghc-ver>-package.conf.d--- stack (snapshots or .stack-work) - library in <path>/lib/<platform-ghc-ver>/<package>--- then package-db is <path>/pkgdb-packageDbCandidate :: FilePath -> IO (Maybe PackageDb)-packageDbCandidate fpath = liftM Just (msum [global', user', sandbox', stack']) `mplus` return Nothing where- global' = do- guard (takeFileName (takeDirectory fpath') == "lib")- guardExist (takeDirectory fpath' </> "package.conf.d")- return GlobalDb- user' = do- cabalDir <- getAppUserDataDirectory "cabal"- guard (splitDirectories cabalDir `isPrefixOf` splitDirectories fpath')- return UserDb- sandbox' = do- guard (".cabal-sandbox" `elem` splitDirectories fpath')- let- sandboxPath = joinPath $ reverse $ dropWhile (/= ".cabal-sandbox") $ reverse $ splitDirectories fpath'- platform = takeFileName (takeDirectory fpath')- dbPath = sandboxPath </> (platform ++ "-packages.conf.d")- guardExist dbPath- return $ PackageDb dbPath- stack' = do- guard (takeFileName (takeDirectory (takeDirectory fpath')) == "lib")- let- pkgDb = takeDirectory (takeDirectory $ takeDirectory fpath') </> "pkgdb"- guardExist pkgDb- return $ PackageDb pkgDb- guardExist = doesDirectoryExist >=> guard- fpath' = normalise fpath---- | Use global as default-packageDbCandidate_ :: FilePath -> IO PackageDb-packageDbCandidate_ = packageDbCandidate >=> maybe (return GlobalDb) return--packageConfigs :: GhcM [GHC.PackageConfig]-packageConfigs = liftM (fromMaybe [] . pkgDatabase) GHC.getSessionDynFlags--packageDbModules :: GhcM [(GHC.PackageConfig, GHC.Module)]-packageDbModules = do- pkgs <- packageConfigs- dflags <- GHC.getSessionDynFlags- return [(p, m) |- p <- pkgs,- mn <- map Compat.exposedModuleName (GHC.exposedModules p),- m <- lookupModule_ dflags mn]---- Lookup module everywhere-lookupModule_ :: GHC.DynFlags -> GHC.ModuleName -> [GHC.Module]-lookupModule_ d mn = case GHC.lookupModuleWithSuggestions d mn Nothing of- GHC.LookupFound m' _ -> [m']- GHC.LookupMultiple ms -> map fst ms- GHC.LookupHidden ls rs -> map fst $ ls ++ rs- GHC.LookupNotFound _ -> []+module HsDev.Scan.Browse ( + -- * List all packages + browsePackages, browsePackagesDeps, + -- * Scan cabal modules + listModules, + browseModules, browseModules', + -- * Helpers + readPackage, readPackageConfig, ghcModuleLocation, + packageConfigs, packageDbModules, lookupModule_, + modulesPackages, modulesPackagesGroups, withEachPackage, + + module Control.Monad.Except + ) where + +import Control.Arrow +import Control.Lens (preview) +import Control.Monad.Catch (MonadCatch, catch, SomeException) +import Control.Monad.Except +import Data.Function (on) +import Data.List (groupBy, sort) +import Data.Maybe +import Data.String (fromString) +import Data.Text (Text) +import qualified Data.Set as S +import Data.Version +import Language.Haskell.Exts.Fixity +import Language.Haskell.Exts.Syntax (Assoc(..), QName(..), Name(Ident), ModuleName(..)) + +import Data.Deps +import Data.LookupTable +import HsDev.PackageDb +import HsDev.Symbols +import HsDev.Error +import HsDev.Tools.Base (inspect) +import HsDev.Tools.Ghc.Worker (GhcM, tmpSession) +import HsDev.Tools.Ghc.Compat as Compat +import HsDev.Util (ordNub) + +import qualified ConLike as GHC +import qualified DataCon as GHC +import qualified DynFlags as GHC +import qualified GHC +import qualified GHC.PackageDb as GHC +import qualified GhcMonad as GHC (liftIO) +import qualified Name as GHC +import qualified IdInfo as GHC +import qualified Outputable as GHC +import qualified Packages as GHC +import qualified PatSyn as GHC +import qualified TyCon as GHC +import qualified Type as GHC +import qualified Var as GHC +import qualified Pretty + +-- | Browse packages +browsePackages :: [String] -> PackageDbStack -> GhcM [PackageConfig] +browsePackages opts dbs = do + tmpSession (packageDbStackOpts dbs ++ opts) + liftM (map readPackageConfig) packageConfigs + +-- | Get packages with deps +browsePackagesDeps :: [String] -> PackageDbStack -> GhcM (Deps PackageConfig) +browsePackagesDeps opts dbs = do + tmpSession (packageDbStackOpts dbs ++ opts) + df <- GHC.getSessionDynFlags + cfgs <- packageConfigs + return $ mapDeps (toPkg df) $ mconcat [deps (Compat.unitId cfg) (Compat.depends df cfg) | cfg <- cfgs] + where + toPkg df' = readPackageConfig . getPackageDetails df' + +-- | List modules from ghc, accepts ghc-opts, stack of package-db to get modules for +-- and list of packages to explicitely expose them with '-package' flag, +-- otherwise hidden packages won't be loaded +listModules :: [String] -> PackageDbStack -> [ModulePackage] -> GhcM [ModuleLocation] +listModules opts dbs pkgs = do + tmpSession (packageDbStackOpts dbs ++ opts ++ packagesOpts) + ms <- packageDbModules + return [ghcModuleLocation p m | (p, m) <- ms] + where + packagesOpts = ["-package " ++ show p | p <- pkgs] + +-- | Like @browseModules@, but groups modules by package and inspects each package separately +-- Trying to fix error: when there are several same packages (of different version), only @Module@ from +-- one of them can be lookuped and therefore modules from different version packages won't be actually inspected +browseModules :: [String] -> PackageDbStack -> [ModuleLocation] -> GhcM [InspectedModule] +browseModules opts dbs mlocs = do + tmpSession (packageDbStackOpts dbs ++ opts) + liftM concat . withEachPackage (const $ browseModules' opts) $ mlocs + +-- | Inspect installed modules, doesn't set session and package flags! +browseModules' :: [String] -> [ModuleLocation] -> GhcM [InspectedModule] +browseModules' opts mlocs = do + ms <- packageDbModules + midTbl <- newLookupTable + sidTbl <- newLookupTable + let + lookupModuleId p' m' = lookupTable (ghcModuleLocation p' m') (ghcModuleId p' m') midTbl + liftM catMaybes $ sequence [browseModule' lookupModuleId (cacheInTableM sidTbl) p m | (p, m) <- ms, ghcModuleLocation p m `S.member` mlocs'] + where + browseModule' :: (GHC.PackageConfig -> GHC.Module -> GhcM ModuleId) -> (GHC.Name -> GhcM Symbol -> GhcM Symbol) -> GHC.PackageConfig -> GHC.Module -> GhcM (Maybe InspectedModule) + browseModule' modId' sym' p m = tryT $ inspect (ghcModuleLocation p m) (return $ InspectionAt 0 (map fromString opts)) (browseModule modId' sym' p m) + mlocs' = S.fromList mlocs + +browseModule :: (GHC.PackageConfig -> GHC.Module -> GhcM ModuleId) -> (GHC.Name -> GhcM Symbol -> GhcM Symbol) -> GHC.PackageConfig -> GHC.Module -> GhcM Module +browseModule modId lookSym package' m = do + df <- GHC.getSessionDynFlags + mi <- GHC.getModuleInfo m >>= maybe (hsdevError $ BrowseNoModuleInfo thisModule) return + ds <- mapM (\n -> lookSym n (toDecl df mi n)) (GHC.modInfoExports mi) + myModId <- modId package' m + let + dirAssoc GHC.InfixL = AssocLeft () + dirAssoc GHC.InfixR = AssocRight () + dirAssoc GHC.InfixN = AssocNone () + fixName o = Qual () (ModuleName () thisModule) (Ident () (GHC.occNameString o)) + return Module { + _moduleId = myModId, + _moduleDocs = Nothing, + _moduleImports = [], + _moduleExports = ds, + _moduleFixities = [Fixity (dirAssoc dir) pr (fixName oname) | (oname, (pr, dir)) <- map (second Compat.getFixity) (maybe [] GHC.mi_fixities (GHC.modInfoIface mi))], + _moduleScope = mempty, + _moduleSource = Nothing } + where + thisModule = GHC.moduleNameString (GHC.moduleName m) + mloc df m' = do + pkg' <- maybe (hsdevError $ OtherError $ "Error getting module package: " ++ GHC.moduleNameString (GHC.moduleName m')) return $ + GHC.lookupPackage df (moduleUnitId m') + modId pkg' m' + toDecl df minfo n = do + tyInfo <- GHC.modInfoLookupName minfo n + tyResult <- maybe (GHC.lookupName n) (return . Just) tyInfo + declModId <- mloc df (GHC.nameModule n) + return Symbol { + _symbolId = SymbolId (fromString $ GHC.getOccString n) declModId, + _symbolDocs = Nothing, + _symbolPosition = Nothing, + _symbolInfo = fromMaybe (Function Nothing) (tyResult >>= showResult df) } + showResult :: GHC.DynFlags -> GHC.TyThing -> Maybe SymbolInfo + showResult dflags (GHC.AnId i) = case GHC.idDetails i of + GHC.RecSelId p _ -> Just $ Selector (Just $ formatType dflags $ GHC.varType i) parent ctors where + parent = fromString $ Compat.recSelParent p + ctors = map fromString $ Compat.recSelCtors p + GHC.ClassOpId cls -> Just $ Method (Just $ formatType dflags $ GHC.varType i) (fromString $ GHC.getOccString cls) + _ -> Just $ Function (Just $ formatType dflags $GHC.varType i) + showResult dflags (GHC.AConLike c) = case c of + GHC.RealDataCon d -> Just $ Constructor + (map (formatType dflags) $ GHC.dataConOrigArgTys d) + (fromString $ GHC.getOccString (GHC.dataConTyCon d)) + GHC.PatSynCon p -> Just $ PatConstructor + (map (formatType dflags) $ GHC.patSynArgs p) + Nothing + -- TODO: Deal with `patSynFieldLabels` and `patSynFieldType` + showResult dflags (GHC.ATyCon t) + | GHC.isTypeSynonymTyCon t = Just $ Type args ctx + | GHC.isPrimTyCon t = Just $ Type [] [] + | GHC.isNewTyCon t = Just $ NewType args ctx + | GHC.isDataTyCon t = Just $ Data args ctx + | GHC.isClassTyCon t = Just $ Class args ctx + | GHC.isTypeFamilyTyCon t = Just $ TypeFam args ctx Nothing + | GHC.isDataFamilyTyCon t = Just $ DataFam args ctx Nothing + | otherwise = Just $ Type [] [] + where + args = map (formatType dflags . GHC.mkTyVarTy) $ GHC.tyConTyVars t + ctx = case GHC.tyConClass_maybe t of + Nothing -> [] + Just cls -> map (formatType dflags) $ GHC.classSCTheta cls + showResult _ _ = Nothing + +formatType :: GHC.DynFlags -> GHC.Type -> Text +formatType dflag t = fromString $ showOutputable dflag (removeForAlls t) + +removeForAlls :: GHC.Type -> GHC.Type +removeForAlls ty = removeForAlls' ty' tty' where + ty' = GHC.dropForAlls ty + tty' = GHC.splitFunTy_maybe ty' + +removeForAlls' :: GHC.Type -> Maybe (GHC.Type, GHC.Type) -> GHC.Type +removeForAlls' ty Nothing = ty +removeForAlls' ty (Just (pre, ftype)) + | GHC.isPredTy pre = GHC.mkFunTy pre (GHC.dropForAlls ftype) + | otherwise = ty + +showOutputable :: GHC.Outputable a => GHC.DynFlags -> a -> String +showOutputable dflag = unwords . lines . showUnqualifiedPage dflag . GHC.ppr + +showUnqualifiedPage :: GHC.DynFlags -> GHC.SDoc -> String +showUnqualifiedPage dflag = renderStyle Pretty.LeftMode 0 . GHC.withPprStyleDoc dflag (Compat.unqualStyle dflag) + +tryT :: MonadCatch m => m a -> m (Maybe a) +tryT act = catch (fmap Just act) (const (return Nothing) . (id :: SomeException -> SomeException)) + +readPackage :: GHC.PackageConfig -> ModulePackage +readPackage pc = ModulePackage (fromString $ GHC.packageNameString pc) (fromString $ showVersion (GHC.packageVersion pc)) + +readPackageConfig :: GHC.PackageConfig -> PackageConfig +readPackageConfig pc = PackageConfig + (readPackage pc) + (map (fromString . GHC.moduleNameString . Compat.exposedModuleName) $ GHC.exposedModules pc) + (GHC.exposed pc) + +ghcModuleLocation :: GHC.PackageConfig -> GHC.Module -> ModuleLocation +ghcModuleLocation p m = InstalledModule (map fromString $ GHC.libraryDirs p) (readPackage p) (fromString $ GHC.moduleNameString $ GHC.moduleName m) + +ghcModuleId :: GHC.PackageConfig -> GHC.Module -> ModuleId +ghcModuleId p m = ModuleId (fromString mname') (ghcModuleLocation p m) where + mname' = GHC.moduleNameString $ GHC.moduleName m + +packageConfigs :: GhcM [GHC.PackageConfig] +packageConfigs = liftM (fromMaybe [] . pkgDatabase) GHC.getSessionDynFlags + +packageDbModules :: GhcM [(GHC.PackageConfig, GHC.Module)] +packageDbModules = do + pkgs <- packageConfigs + dflags <- GHC.getSessionDynFlags + return [(p, m) | + p <- pkgs, + mn <- map Compat.exposedModuleName (GHC.exposedModules p), + m <- lookupModule_ dflags mn] + +-- Lookup module everywhere +lookupModule_ :: GHC.DynFlags -> GHC.ModuleName -> [GHC.Module] +lookupModule_ d mn = case GHC.lookupModuleWithSuggestions d mn Nothing of + GHC.LookupFound m' _ -> [m'] + GHC.LookupMultiple ms -> map fst ms + GHC.LookupHidden ls rs -> map fst $ ls ++ rs + GHC.LookupNotFound _ -> [] + +-- | Get modules packages +modulesPackages :: [ModuleLocation] -> [ModulePackage] +modulesPackages = ordNub . mapMaybe (preview modulePackage) + +-- | Group modules by packages +modulesPackagesGroups :: [ModuleLocation] -> [(ModulePackage, [ModuleLocation])] +modulesPackagesGroups = map (first head . unzip) . groupBy ((==) `on` fst) . sort . mapMaybe (\m -> (,) <$> preview modulePackage m <*> pure m) + +-- | Run action for each package with prepared '-package' flags +withEachPackage :: (ModulePackage -> [ModuleLocation] -> GhcM a) -> [ModuleLocation] -> GhcM [a] +withEachPackage act = mapM (uncurry act') . modulesPackagesGroups where + act' mpkg mlocs = setPackagesOpts >> act mpkg mlocs where + packagesOpts = "-hide-all-packages" : ["-package " ++ show p | p <- modulesPackages mlocs] + setPackagesOpts = void $ do + fs <- GHC.getSessionDynFlags + (fs', _, _) <- GHC.parseDynamicFlags (fs { GHC.packageFlags = [] }) (map GHC.noLoc packagesOpts) + (fs'', _) <- GHC.liftIO $ GHC.initPackages fs' + GHC.setSessionDynFlags fs''
src/HsDev/Server/Base.hs view
@@ -1,159 +1,395 @@-{-# LANGUAGE CPP, OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Server.Base (- initLog, runServer, Server, startServer, inServer,- withCache, writeCache, readCache,-- module HsDev.Server.Types,- module HsDev.Server.Message- ) where--import Control.Applicative-import Control.Concurrent-import Control.Exception-import Control.Monad-import Control.Monad.Except-import Control.Monad.Reader-import Data.Default-import qualified Data.Map as M-import Data.Maybe-import Data.String-import Data.Text (Text)-import qualified Data.Text as T (pack, unpack)-import System.Log.Simple hiding (Level(..), Message)-import qualified System.Log.Simple.Base as Log-import System.Directory (removeDirectoryRecursive, createDirectoryIfMissing)-import System.FilePath--import qualified Control.Concurrent.FiniteChan as F-import System.Directory.Paths (canonicalize)-import qualified System.Directory.Watcher as Watcher-import Text.Format ((~~), (~%))--import qualified HsDev.Cache as Cache-import qualified HsDev.Cache.Structured as SC-import qualified HsDev.Client.Commands as Client-import HsDev.Database-import qualified HsDev.Database.Async as DB-import qualified HsDev.Database.Update as Update-import HsDev.Inspect (getDefines)-import HsDev.Tools.Ghc.Worker-import HsDev.Server.Types-import HsDev.Server.Message-import HsDev.Util--#if mingw32_HOST_OS-import System.Win32.FileMapping.NamePool-#endif---- | Inits log chan and returns functions (print message, wait channel)-initLog :: ServerOpts -> IO SessionLog-initLog sopts = do- msgs <- F.newChan- l <- newLog (logCfg [("", Log.level_ . T.pack . serverLogLevel $ sopts)]) $ concat [- [handler text console | not $ serverSilent sopts],- [handler text (chaner msgs)],- [handler text (file f) | f <- maybeToList (serverLog sopts)]]- let- listenLog = F.dupChan msgs >>= F.readChan- return $ SessionLog l listenLog (stopLog l)---- | Run server-runServer :: ServerOpts -> ServerM IO () -> IO ()-runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Watcher.withWatcher $ \watcher -> withLog (sessionLogger slog) $ do- waitSem <- liftIO $ newQSem 0- db <- liftIO $ DB.newAsync- withCache sopts () $ \cdir -> do- sendLog Log.Trace $ "Checking cache version in {}" ~~ cdir - ver <- liftIO $ Cache.readVersion $ cdir </> Cache.versionCache- sendLog Log.Debug $ "Cache version: {}" ~~ strVersion ver- unless (sameVersion (cutVersion version) (cutVersion ver)) $ ignoreIO $ do- sendLog Log.Info $ "Cache version ({cache}) is incompatible with hsdev version ({hsdev}), removing cache ({dir})" ~~- ("cache" ~% strVersion ver) ~~- ("hsdev" ~% strVersion version) ~~- ("dir" ~% cdir)- -- drop cache- liftIO $ removeDirectoryRecursive cdir- sendLog Log.Debug $ "Writing new cache version: {}" ~~ strVersion version- liftIO $ createDirectoryIfMissing True cdir- liftIO $ Cache.writeVersion $ cdir </> Cache.versionCache- when (serverLoad sopts) $ withCache sopts () $ \cdir -> do- sendLog Log.Info $ "Loading cache from {}" ~~ cdir- dbCache <- liftA merge <$> liftIO (SC.load cdir)- case dbCache of- Left err -> sendLog Log.Error $ "Failed to load cache: {}" ~~ err- Right dbCache' -> DB.update db (return dbCache')-#if mingw32_HOST_OS- mmapPool <- Just <$> liftIO (createPool "hsdev")-#endif- ghcw <- ghcWorker- defs <- liftIO getDefines- let- session = Session- db- (writeCache sopts)- (readCache sopts)- slog- watcher-#if mingw32_HOST_OS- mmapPool-#endif- ghcw- (do- withLog (sessionLogger slog) $ sendLog Log.Trace "stopping server"- signalQSem waitSem)- (waitQSem waitSem)- defs- _ <- liftIO $ forkIO $ Update.onEvent watcher $ \w e -> withSession session $- void $ Client.runClient def $ Update.processEvent def w e- liftIO $ runReaderT (runServerM act) session--type Server = Worker (ServerM IO)--startServer :: ServerOpts -> IO Server-startServer sopts = startWorker (runServer sopts) id id--inServer :: Server -> CommandOptions -> Command -> IO Result-inServer srv copts c = do- c' <- canonicalize c- inWorker srv (Client.runClient copts $ Client.runCommand c')--chaner :: F.Chan String -> Consumer Text-chaner ch = return $ F.putChan ch . T.unpack---- | Perform action on cache-withCache :: Monad m => ServerOpts -> a -> (FilePath -> m a) -> m a-withCache sopts v onCache = case serverCache sopts of- Nothing -> return v- Just cdir -> onCache cdir--writeCache :: SessionMonad m => ServerOpts -> Database -> m ()-writeCache sopts db = withCache sopts () $ \cdir -> do- sendLog Log.Info $ "writing cache to {}" ~~ cdir- logIO "cache writing exception: " (sendLog Log.Error . fromString) $ do- let- sd = structurize db- liftIO $ SC.dump cdir sd- forM_ (M.keys (structuredPackageDbs sd)) $ \c -> sendLog Log.Debug ("cache write: cabal {}" ~~ show c)- forM_ (M.keys (structuredProjects sd)) $ \p -> sendLog Log.Debug ("cache write: project {}" ~~ p)- case allModules (structuredFiles sd) of- [] -> return ()- ms -> sendLog Log.Debug $ "cache write: {} files" ~~ length ms- sendLog Log.Info $ "cache saved to {}" ~~ cdir--readCache :: SessionMonad m => ServerOpts -> (FilePath -> ExceptT String IO Structured) -> m (Maybe Database)-readCache sopts act = do- s <- getSession- liftIO $ withSession s $ withCache sopts Nothing $ \fpath -> do- res <- liftIO $ runExceptT $ act fpath- either cacheErr cacheOk res- where- cacheErr e = sendLog Log.Error ("Error reading cache: {}" ~~ e) >> return Nothing- cacheOk s = do- forM_ (M.keys (structuredPackageDbs s)) $ \c -> sendLog Log.Debug ("cache read: cabal {}" ~~ show c)- forM_ (M.keys (structuredProjects s)) $ \p -> sendLog Log.Debug ("cache read: project {}" ~~ p)- case allModules (structuredFiles s) of- [] -> return ()- ms -> sendLog Log.Debug $ "cache read: {} files" ~~ length ms- return $ Just $ merge s+{-# LANGUAGE CPP, OverloadedStrings, CPP, PatternGuards #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Server.Base ( + initLog, runServer, Server, + setupServer, shutdownServer, + startServer, startServer_, stopServer, withServer, withServer_, inServer, clientCommand, parseCommand, readCommand, + sendServer, sendServer_, + findPath, + processRequest, processClient, processClientSocket, + + unMmap, makeSocket, sockAddr, + + module HsDev.Server.Types, + module HsDev.Server.Message + ) where + +import Control.Concurrent +import Control.Concurrent.Async +import qualified Control.Concurrent.Chan as C +import Control.Lens (set, traverseOf, view) +import Control.Monad +import Control.Monad.Loops +import Control.Monad.Except +import Control.Monad.Reader +import Control.Monad.Catch (bracket_, bracket, finally) +import Data.Aeson hiding (Result, Error) +import Data.Default +import qualified Data.ByteString.Char8 as BS +import Data.ByteString.Lazy.Char8 (ByteString) +import qualified Data.ByteString.Lazy.Char8 as L +import Data.Maybe +import Data.String (fromString) +import qualified Data.Text as T (pack) +import Data.Time.Clock.POSIX +import Options.Applicative (info, progDesc) +import System.Log.Simple hiding (Level(..), Message) +import qualified System.Log.Simple.Base as Log (level_) +import qualified System.Log.Simple as Log +import Network.Socket +import qualified Network.Socket.ByteString as Net (send) +import qualified Network.Socket.ByteString.Lazy as Net (getContents) +import System.FilePath +import System.IO +import Text.Format ((~~)) + +import Control.Concurrent.Util +import qualified Control.Concurrent.FiniteChan as F +import System.Directory.Paths +import qualified System.Directory.Watcher as Watcher + +import qualified HsDev.Client.Commands as Client +import qualified HsDev.Database.SQLite as SQLite +import HsDev.Error +import qualified HsDev.Database.Update as Update +import HsDev.Inspect (getDefines) +import HsDev.Tools.Ghc.Worker +import HsDev.Server.Types +import HsDev.Server.Message +import HsDev.Symbols.Location (ModuleLocation(..)) +import qualified HsDev.Watcher as W +import HsDev.Util + +#if mingw32_HOST_OS +import Data.Aeson.Types hiding (Result, Error) +import System.Win32.FileMapping.Memory (withMapFile, readMapFile) +import System.Win32.FileMapping.NamePool +#else +import System.Posix.Files (removeLink) +#endif + +-- | Inits log chan and returns functions (print message, wait channel) +initLog :: ServerOpts -> IO SessionLog +initLog sopts = do + msgs <- C.newChan + l <- newLog (logCfg [("", Log.level_ . T.pack . serverLogLevel $ sopts)]) $ concat [ + [handler text coloredConsole | not $ serverSilent sopts], + [chaner msgs], + [handler text (file f) | f <- maybeToList (serverLog sopts)]] + let + listenLog = C.dupChan msgs >>= C.getChanContents + return $ SessionLog l listenLog (stopLog l) + +-- | Run server +runServer :: ServerOpts -> ServerM IO () -> IO () +runServer sopts act = bracket (initLog sopts) sessionLogWait $ \slog -> Watcher.withWatcher $ \watcher -> withLog (sessionLogger slog) $ do + waitSem <- liftIO $ newQSem 0 + sqlDb <- liftIO $ SQLite.initialize (fromMaybe SQLite.sharedMemory $ serverDbFile sopts) + clientChan <- liftIO F.newChan +#if mingw32_HOST_OS + mmapPool <- Just <$> liftIO (createPool "hsdev") +#endif + ghcw <- ghcWorker + defs <- liftIO getDefines + + session <- liftIO $ fixIO $ \sess -> do + let + setFileCts fpath Nothing = withSession sess $ do + Log.sendLog Log.Trace $ "dropping file contents for {}" ~~ fpath + SQLite.execute "delete from file_contents where file = ?;" (SQLite.Only fpath) + setFileCts fpath (Just cts) = do + tm <- getPOSIXTime + withSession sess $ do + Log.sendLog Log.Trace $ "setting file contents for {} with mtime = {}" ~~ fpath ~~ show tm + SQLite.execute "insert or replace into file_contents (file, contents, mtime) values (?, ?, ?);" (fpath, cts, (fromRational (toRational tm) :: Double)) + writeChan (W.watcherChan watcher) (W.WatchedModule, W.Event W.Modified (view path fpath) tm) + + uw <- startWorker (withSession sess . withSqlConnection) id logAll + + return $ Session + sqlDb + (fromMaybe SQLite.sharedMemory $ serverDbFile sopts) + slog + watcher + setFileCts +#if mingw32_HOST_OS + mmapPool +#endif + ghcw + uw + (do + withLog (sessionLogger slog) $ Log.sendLog Log.Trace "stopping server" + signalQSem waitSem) + (waitQSem waitSem) + clientChan + defs + + _ <- fork $ do + emptyTask <- async $ return () + updaterTask <- newMVar emptyTask + tasksVar <- newMVar [] + Update.onEvents_ watcher $ \evs -> withSession session $ + void $ Client.runClient def $ Update.processEvents (withSession session . inSessionUpdater . void . Client.runClient def . Update.applyUpdates def) updaterTask tasksVar evs + liftIO $ runReaderT (runServerM $ watchDb >> act) session + +-- | Set initial watch: package-dbs, projects and standalone sources +watchDb :: SessionMonad m => m () +watchDb = do + w <- askSession sessionWatcher + -- TODO: Implement watching package-dbs + cabals <- SQLite.query_ "select cabal from projects;" + projects <- mapM (SQLite.loadProject . SQLite.fromOnly) cabals + liftIO $ mapM_ (\proj -> W.watchProject w proj []) projects + + files <- SQLite.query_ "select file from modules where file is not null and cabal is null;" + liftIO $ mapM_ (\(SQLite.Only f) -> W.watchModule w (FileModule f Nothing)) files + +type Server = Worker (ServerM IO) + +-- | Start listening for incoming connections +setupServer :: ServerOpts -> ServerM IO () +setupServer sopts = do + q <- liftIO $ newQSem 0 + clientChan <- askSession sessionClients + session <- getSession + _ <- liftIO $ async $ withSession session $ Log.scope "listener" $ flip finally serverExit $ + bracket (liftIO $ makeSocket (serverPort sopts)) (liftIO . close) $ \s -> do + liftIO $ do + setSocketOption s ReuseAddr 1 + addr' <- inet_addr "127.0.0.1" + bind s (sockAddr (serverPort sopts) addr') + listen s maxListenQueue + forever $ logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $ do + Log.sendLog Log.Trace "accepting connection..." + liftIO $ signalQSem q + (s', addr') <- liftIO $ accept s + Log.sendLog Log.Trace $ "accepted {}" ~~ show addr' + fork $ withSession session $ Log.scope (T.pack $ show addr') $ + logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $ + flip finally (liftIO $ close s') $ + bracket (liftIO newEmptyMVar) (liftIO . (`putMVar` ())) $ \done -> do + me <- liftIO myThreadId + let + timeoutWait = withSession session $ do + notDone <- liftIO $ isEmptyMVar done + when notDone $ do + Log.sendLog Log.Trace $ "waiting for {} to complete" ~~ show addr' + waitAsync <- liftIO $ async $ do + threadDelay 1000000 + killThread me + liftIO $ void $ waitCatch waitAsync + liftIO $ void $ F.sendChan clientChan timeoutWait + processClientSocket (show addr') s' + + Log.sendLog Log.Trace "waiting for starting accept thread..." + liftIO $ waitQSem q + logIO "error writing to stdout: " (Log.sendLog Log.Error . fromString) $ liftIO $ putStrLn $ "Server started at port {}" ~~ serverPort sopts + Log.sendLog Log.Info $ "Server started at port {}" ~~ serverPort sopts + +-- | Shutdown server +shutdownServer :: ServerOpts -> ServerM IO () +shutdownServer sopts = do + Log.sendLog Log.Trace "waiting for accept thread..." + serverWait + Log.sendLog Log.Trace "accept thread stopped" + liftIO $ unlink (serverPort sopts) + Log.sendLog Log.Trace "waiting for clients..." + serverWaitClients + Log.sendLog Log.Info "server stopped" + +startServer :: ServerOpts -> IO Server +startServer sopts = startWorker (runServer sopts) (bracket_ (setupServer sopts) (shutdownServer sopts)) logAll + +-- Tiny version with no network stuff +startServer_ :: ServerOpts -> IO Server +startServer_ sopts = startWorker (runServer sopts) id logAll + +stopServer :: Server -> IO () +stopServer s = sendServer_ s ["exit"] >> stopWorker s + +withServer :: ServerOpts -> (Server -> IO a) -> IO a +withServer sopts = bracket (startServer sopts) stopServer + +withServer_ :: ServerOpts -> (Server -> IO a) -> IO a +withServer_ sopts = bracket (startServer_ sopts) stopServer + +inServer :: Server -> ServerM IO a -> IO a +inServer = inWorker + +clientCommand :: CommandOptions -> Command -> ServerM IO Result +clientCommand copts c = do + c' <- liftIO $ canonicalize c + Client.runClient copts (Client.runCommand c') + +parseCommand :: [String] -> Either String Command +parseCommand = parseArgs "hsdev" (info cmdP (progDesc "hsdev tool")) + +readCommand :: [String] -> Command +readCommand = either error id . parseCommand + +sendServer :: Server -> CommandOptions -> [String] -> IO Result +sendServer srv copts args = case parseCommand args of + Left e -> hsdevError $ RequestError e (unwords args) + Right c -> inServer srv (clientCommand copts c) + +sendServer_ :: Server -> [String] -> IO Result +sendServer_ srv = sendServer srv def + +chaner :: C.Chan Log.Message -> Consumer Log.Message +chaner ch = return $ C.writeChan ch + +findPath :: MonadIO m => CommandOptions -> FilePath -> m FilePath +findPath copts f = liftIO $ canonicalize (normalise f') where + f' = absolutise (fromFilePath $ commandOptionsRoot copts) f + +-- | Process request, notifications can be sent during processing +processRequest :: SessionMonad m => CommandOptions -> Command -> m Result +processRequest copts c = do + c' <- paths (findPath copts) c + s <- getSession + withSession s $ Client.runClient copts $ Client.runCommand c' + +-- | Process client, listen for requests and process them +processClient :: SessionMonad m => String -> F.Chan ByteString -> (ByteString -> IO ()) -> m () +processClient name rchan send' = do + Log.sendLog Log.Info "connected" + respChan <- liftIO newChan + fork $ getChanContents respChan >>= mapM_ (send' . encodeMessage) + linkVar <- liftIO $ newMVar $ return () + s <- getSession + exit <- askSession sessionExit + let + answer :: SessionMonad m => Msg (Message Response) -> m () + answer m = do + unless (isNotification $ view (msg . message) m) $ + Log.sendLog Log.Trace $ "responsed << {}" ~~ ellipsis (fromUtf8 (encode $ view (msg . message) m)) + liftIO $ writeChan respChan m + where + ellipsis :: String -> String + ellipsis str + | length str < 100 = str + | otherwise = take 100 str ++ "..." + + flip finally (disconnected linkVar) $ + whileJust_ (liftIO $ F.getChan rchan) $ \req' -> do + Log.sendLog Log.Trace $ "received >> {}" ~~ fromUtf8 req' + case decodeMessage req' of + Left em -> do + Log.sendLog Log.Warning $ "Invalid request {}" ~~ fromUtf8 req' + answer $ set msg (Message Nothing $ responseError $ RequestError "invalid request" $ fromUtf8 req') em + Right m -> fork $ withSession s $ Log.scope (T.pack name) $ Log.scope "req" $ + Log.scope (T.pack $ fromMaybe "_" (view (msg . messageId) m)) $ do + resp' <- flip (traverseOf (msg . message)) m $ \(Request c cdir noFile tm silent) -> do + let + onNotify n + | silent = return () + | otherwise = traverseOf (msg . message) (const $ mmap' noFile (Response $ Left n)) m >>= answer + Log.sendLog Log.Trace $ "requested >> {}" ~~ fromUtf8 (encode c) + resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ hsdevLiftIO $ withSession s $ + processRequest + CommandOptions { + commandOptionsRoot = cdir, + commandOptionsNotify = withSession s . onNotify, + commandOptionsLink = void (swapMVar linkVar exit), + commandOptionsHold = forever (F.getChan rchan) } + c + mmap' noFile resp + answer resp' + where + handleTimeout :: Int -> IO Result -> IO Result + handleTimeout 0 = id + handleTimeout tm = fmap (fromMaybe $ Error $ OtherError "timeout") . timeout tm + + mmap' :: SessionMonad m => Bool -> Response -> m Response +#if mingw32_HOST_OS + mmap' False r = do + mpool <- askSession sessionMmapPool + case mpool of + Just pool -> liftIO $ mmap pool r + Nothing -> return r +#endif + mmap' _ r = return r + + -- Call on disconnected, either no action or exit command + disconnected :: SessionMonad m => MVar (IO ()) -> m () + disconnected var = do + Log.sendLog Log.Info "disconnected" + liftIO $ join $ takeMVar var + +-- | Process client by socket +processClientSocket :: SessionMonad m => String -> Socket -> m () +processClientSocket name s = do + recvChan <- liftIO F.newChan + fork $ finally + (Net.getContents s >>= mapM_ (F.sendChan recvChan) . L.lines) + (F.closeChan recvChan) + processClient name recvChan (sendLine s) + where + -- NOTE: Network version of `sendAll` goes to infinite loop on client socket close + -- when server's send is blocked, see https://github.com/haskell/network/issues/155 + -- After that issue fixed we may revert to `processClientHandle` + sendLine :: Socket -> ByteString -> IO () + sendLine sock bs = sendAll sock $ L.toStrict $ L.snoc bs '\n' + sendAll :: Socket -> BS.ByteString -> IO () + sendAll sock bs + | BS.null bs = return () + | otherwise = do + sent <- Net.send sock bs + when (sent > 0) $ sendAll sock (BS.drop sent bs) + +#if mingw32_HOST_OS +newtype MmapFile = MmapFile String + +instance ToJSON MmapFile where + toJSON (MmapFile f) = object ["file" .= f] + +instance FromJSON MmapFile where + parseJSON = withObject "file" $ \v -> MmapFile <$> v .:: "file" + +-- | Push message to mmap and return response which points to this mmap +mmap :: Pool -> Response -> IO Response +mmap mmapPool r + | L.length msg' <= 1024 = return r + | otherwise = do + rvar <- newEmptyMVar + _ <- forkIO $ flip finally (tryPutMVar rvar r) $ void $ withName mmapPool $ \mmapName -> runExceptT $ catchError + (withMapFile mmapName (L.toStrict msg') $ liftIO $ do + _ <- tryPutMVar rvar $ result $ MmapFile mmapName + -- give 10 seconds for client to read data + threadDelay 10000000) + (\_ -> liftIO $ void $ tryPutMVar rvar r) + takeMVar rvar + where + msg' = encode r +#endif + +-- | If response points to mmap, get its contents and parse +unMmap :: Response -> IO Response +#if mingw32_HOST_OS +unMmap (Response (Right (Result v))) + | Just (MmapFile f) <- parseMaybe parseJSON v = do + cts <- runExceptT (fmap L.fromStrict (readMapFile f)) + case cts of + Left _ -> return $ responseError $ ResponseError "can't read map view of file" f + Right r' -> case eitherDecode r' of + Left e' -> return $ responseError $ ResponseError ("can't parse response: {}" ~~ e') (fromUtf8 r') + Right r'' -> return r'' +#endif +unMmap r = return r + +makeSocket :: ConnectionPort -> IO Socket +makeSocket (NetworkPort _) = socket AF_INET Stream defaultProtocol +makeSocket (UnixPort _) = socket AF_UNIX Stream defaultProtocol + +sockAddr :: ConnectionPort -> HostAddress -> SockAddr +sockAddr (NetworkPort p) addr = SockAddrInet (fromIntegral p) addr +sockAddr (UnixPort s) _ = SockAddrUnix s + +unlink :: ConnectionPort -> IO () +unlink (NetworkPort _) = return () +#if mingw32_HOST_OS +unlink (UnixPort _) = return () +#else +unlink (UnixPort s) = removeLink s +#endif
src/HsDev/Server/Commands.hs view
@@ -1,434 +1,180 @@-{-# LANGUAGE OverloadedStrings, CPP, PatternGuards, LambdaCase, TemplateHaskell #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Server.Commands (- ServerCommand(..), ServerOpts(..), ClientOpts(..),- Request(..),- Msg, isLisp, msg, jsonMsg, lispMsg, encodeMessage, decodeMessage,- sendCommand, runServerCommand,- findPath,- processRequest, processClient, processClientSocket,- module HsDev.Server.Types- ) where--import Control.Applicative-import Control.Concurrent-import Control.Concurrent.Async-import Control.Lens (set, traverseOf, view, over, Lens', Lens, _1, _2, _Left)-import Control.Monad-import Control.Monad.Catch (bracket, finally)-import Data.Aeson hiding (Result, Error)-import Data.Aeson.Encode.Pretty-import qualified Data.ByteString.Char8 as BS-import Data.ByteString.Lazy.Char8 (ByteString)-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Maybe-import Data.String (fromString)-import qualified Data.Text as T (pack)-import Network.Socket hiding (connect)-import qualified Network.Socket as Net hiding (send)-import qualified Network.Socket.ByteString as Net (send)-import qualified Network.Socket.ByteString.Lazy as Net (getContents)-import System.Directory-import System.Exit-import System.FilePath-import System.IO-import qualified System.Log.Simple as Log--import Control.Concurrent.Util-import qualified Control.Concurrent.FiniteChan as F-import Data.Lisp-import Text.Format ((~~), (~%))-import System.Directory.Paths--import qualified HsDev.Client.Commands as Client-import qualified HsDev.Database.Async as DB-import HsDev.Server.Base-import HsDev.Server.Types-import HsDev.Tools.Base (runTool_)-import HsDev.Error-import HsDev.Util-import HsDev.Version--#if mingw32_HOST_OS-import Data.Aeson.Types hiding (Result, Error)-import Data.Char-import Data.List-import System.Environment-import System.Win32.FileMapping.Memory (withMapFile, readMapFile)-import System.Win32.FileMapping.NamePool-import System.Win32.PowerShell (escape, quote, quoteDouble)-#else-import Control.Exception (SomeException, handle)-import System.Posix.Process-import System.Posix.Files (removeLink)-import System.Posix.IO-#endif--sendCommand :: ClientOpts -> Bool -> Command -> (Notification -> IO a) -> IO Result-sendCommand copts noFile c onNotification = do- asyncAct <- async sendReceive- res <- waitCatch asyncAct- case res of- Left e -> return $ Error $ OtherError (show e)- Right r -> return r- where- sendReceive = do- curDir <- getCurrentDirectory- input <- if clientStdin copts- then Just <$> L.getContents- else return $ toUtf8 <$> Nothing -- arg "data" copts- let- parseData :: L.ByteString -> IO Value- parseData cts = case eitherDecode cts of- Left err -> putStrLn ("Invalid data: " ++ err) >> exitFailure- Right v -> return v- _ <- traverse parseData input -- FIXME: Not used!-- s <- makeSocket (clientPort copts)- addr' <- inet_addr "127.0.0.1"- Net.connect s (sockAddr (clientPort copts) addr')- bracket (socketToHandle s ReadWriteMode) hClose $ \h -> do- L.hPutStrLn h $ encode $ Message Nothing $ Request c curDir noFile (clientTimeout copts) (clientSilent copts)- hFlush h- peekResponse h-- peekResponse h = do- resp <- hGetLineBS h- parseResponse h resp-- parseResponse h str = case eitherDecode str of- Left e -> return $ Error $ ResponseError ("can't parse: {}" ~~ e) (fromUtf8 str)- Right (Message _ r) -> do- Response r' <- unMmap r- case r' of- Left n -> onNotification n >> peekResponse h- Right res -> return res--runServerCommand :: ServerCommand -> IO ()-runServerCommand Version = putStrLn $cabalVersion-runServerCommand (Start sopts) = do-#if mingw32_HOST_OS- let- args = "run" : serverOptsArgs sopts- myExe <- getExecutablePath- curDir <- getCurrentDirectory- let- -- one escape for start-process and other for callable process- -- seems, that start-process just concats arguments into one string- -- start-process foo 'bar baz' ⇒ foo bar baz -- not expected- -- start-process foo '"bar baz"' ⇒ foo "bar baz" -- ok- biescape = escape quote . escape quoteDouble- script = "try {{ start-process {process} {args} -WindowStyle Hidden -WorkingDirectory {dir} }} catch {{ $_.Exception, $_.InvocationInfo.Line }}"- ~~ ("process" ~% escape quote myExe)- ~~ ("args" ~% intercalate ", " (map biescape args))- ~~ ("dir" ~% escape quote curDir)- r <- runTool_ "powershell" [- "-Command",- script]- if all isSpace r- then putStrLn $ "Server started at port {}" ~~ serverPort sopts- else mapM_ putStrLn [- "Failed to start server",- "\tCommand: {}" ~~ script,- "\tResult: {}" ~~ r]-#else- let- forkError :: SomeException -> IO ()- forkError e = putStrLn $ "Failed to start server: {}" ~~ show e-- proxy :: IO ()- proxy = do- _ <- createSession- _ <- forkProcess serverAction- exitImmediately ExitSuccess-- serverAction :: IO ()- serverAction = do- mapM_ closeFd [stdInput, stdOutput, stdError]- nullFd <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags- mapM_ (dupTo nullFd) [stdInput, stdOutput, stdError]- closeFd nullFd- runServerCommand (Run sopts)-- handle forkError $ do- _ <- forkProcess proxy- putStrLn $ "Server started at port {}" ~~ serverPort sopts-#endif-runServerCommand (Run sopts) = runServer sopts $ do- q <- liftIO $ newQSem 0- clientChan <- liftIO F.newChan- session <- getSession- _ <- liftIO $ async $ withSession session $ Log.scope "listener" $ flip finally serverExit $- bracket (liftIO $ makeSocket (serverPort sopts)) (liftIO . close) $ \s -> do- liftIO $ do- setSocketOption s ReuseAddr 1- addr' <- inet_addr "127.0.0.1"- bind s (sockAddr (serverPort sopts) addr')- listen s maxListenQueue- forever $ logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $ do- Log.sendLog Log.Trace "accepting connection..."- liftIO $ signalQSem q- (s', addr') <- liftIO $ accept s- Log.sendLog Log.Trace $ "accepted {}" ~~ show addr'- void $ liftIO $ forkIO $ withSession session $ Log.scope (T.pack $ show addr') $- logAsync (Log.sendLog Log.Fatal . fromString) $ logIO "exception: " (Log.sendLog Log.Error . fromString) $- flip finally (liftIO $ close s') $- bracket (liftIO newEmptyMVar) (liftIO . (`putMVar` ())) $ \done -> do- me <- liftIO myThreadId- let- timeoutWait = withSession session $ do- notDone <- liftIO $ isEmptyMVar done- when notDone $ do- Log.sendLog Log.Trace $ "waiting for {} to complete" ~~ show addr'- waitAsync <- liftIO $ async $ do- threadDelay 1000000- killThread me- liftIO $ void $ waitCatch waitAsync- liftIO $ F.putChan clientChan timeoutWait- processClientSocket (show addr') s'-- Log.sendLog Log.Trace "waiting for starting accept thread..."- liftIO $ waitQSem q- liftIO $ putStrLn $ "Server started at port {}" ~~ serverPort sopts- Log.sendLog Log.Info $ "server started at port {}" ~~ serverPort sopts- Log.sendLog Log.Trace "waiting for accept thread..."- serverWait- Log.sendLog Log.Trace "accept thread stopped"- liftIO $ unlink (serverPort sopts)- askSession sessionDatabase >>= liftIO . DB.readAsync >>= writeCache sopts- Log.sendLog Log.Trace "waiting for clients..."- liftIO (F.stopChan clientChan) >>= sequence_- Log.sendLog Log.Info "server stopped"-runServerCommand (Stop copts) = runServerCommand (Remote copts False Exit)-runServerCommand (Connect copts) = do- curDir <- getCurrentDirectory- s <- makeSocket $ clientPort copts- addr' <- inet_addr "127.0.0.1"- Net.connect s $ sockAddr (clientPort copts) addr'- bracket (socketToHandle s ReadWriteMode) hClose $ \h -> forM_ [(1 :: Integer)..] $ \i -> ignoreIO $ do- input' <- hGetLineBS stdin- case decodeMsg input' of- Left em -> L.putStrLn $ encodeMessage $ set msg (Message Nothing $ responseError $ OtherError "invalid command") em- Right m -> do- L.hPutStrLn h $ encodeMessage $ set msg (Message (Just $ show i) $ Request (view msg m) curDir True (clientTimeout copts) False) m- waitResp h- where- waitResp h = do- resp <- hGetLineBS h- parseResp h resp-- parseResp h str = case decodeMessage str of- Left em -> putStrLn $ "Can't decode response: {}" ~~ view msg em- Right m -> do- Response r' <- unMmap $ view (msg . message) m- putStrLn $ "{id}: {response}"- ~~ ("id" ~% fromMaybe "_" (view (msg . messageId) m))- ~~ ("response" ~% fromUtf8 (encodeMsg $ set msg (Response r') m))- case unResponse (view (msg . message) m) of- Left _ -> waitResp h- _ -> return ()-runServerCommand (Remote copts noFile c) = sendCommand copts noFile c printValue >>= printResult where- printValue :: ToJSON a => a -> IO ()- printValue = L.putStrLn . encodeValue- printResult :: Result -> IO ()- printResult (Result r) = printValue r- printResult e = printValue e- encodeValue :: ToJSON a => a -> L.ByteString- encodeValue = if clientPretty copts then encodePretty else encode--findPath :: MonadIO m => CommandOptions -> FilePath -> m FilePath-findPath copts f = liftIO $ canonicalizePath (normalise f') where- f'- | isRelative f = commandOptionsRoot copts </> f- | otherwise = f--type Msg a = (Bool, a)--isLisp :: Lens' (Msg a) Bool-isLisp = _1--msg :: Lens (Msg a) (Msg b) a b-msg = _2--jsonMsg :: a -> Msg a-jsonMsg = (,) False--lispMsg :: a -> Msg a-lispMsg = (,) True---- | Decode lisp or json-decodeMsg :: FromJSON a => ByteString -> Either (Msg String) (Msg a)-decodeMsg bstr = over _Left decodeType' decodeMsg' where- decodeType'- | isLisp' = lispMsg- | otherwise = jsonMsg- decodeMsg' = (lispMsg <$> decodeLisp bstr) <|> (jsonMsg <$> eitherDecode bstr)- isLisp' = fromMaybe False $ mplus (try' eitherDecode False) (try' decodeLisp True)- try' :: (ByteString -> Either String Value) -> Bool -> Maybe Bool- try' f l = either (const Nothing) (const $ Just l) $ f bstr---- | Encode lisp or json-encodeMsg :: ToJSON a => Msg a -> ByteString-encodeMsg m- | view isLisp m = encodeLisp $ view msg m- | otherwise = encode $ view msg m---- | Decode lisp or json request-decodeMessage :: FromJSON a => ByteString -> Either (Msg String) (Msg (Message a))-decodeMessage = decodeMsg--encodeMessage :: ToJSON a => Msg (Message a) -> ByteString-encodeMessage = encodeMsg---- | Process request, notifications can be sent during processing-processRequest :: SessionMonad m => CommandOptions -> Command -> m Result-processRequest copts c = do- c' <- paths (findPath copts) c- s <- getSession- withSession s $ Client.runClient copts $ Client.runCommand c'---- | Process client, listen for requests and process them-processClient :: SessionMonad m => String -> F.Chan ByteString -> (ByteString -> IO ()) -> m ()-processClient name rchan send' = do- Log.sendLog Log.Info "connected"- respChan <- liftIO newChan- liftIO $ void $ forkIO $ getChanContents respChan >>= mapM_ (send' . encodeMessage)- linkVar <- liftIO $ newMVar $ return ()- s <- getSession- exit <- askSession sessionExit- let- answer :: SessionMonad m => Msg (Message Response) -> m ()- answer m = do- unless (isNotification $ view (msg . message) m) $- Log.sendLog Log.Trace $ "responsed << {}" ~~ ellipsis (fromUtf8 (encode $ view (msg . message) m))- liftIO $ writeChan respChan m- where- ellipsis :: String -> String- ellipsis str- | length str < 100 = str- | otherwise = take 100 str ++ "..."- -- flip finally (disconnected linkVar) $ forever $ Log.scopeLog (commandLogger copts) (T.pack name) $ do- reqs <- liftIO $ F.readChan rchan- flip finally (disconnected linkVar) $- forM_ reqs $ \req' -> do- Log.sendLog Log.Trace $ "received >> {}" ~~ fromUtf8 req'- case decodeMessage req' of- Left em -> do- Log.sendLog Log.Warning $ "Invalid request {}" ~~ fromUtf8 req'- answer $ set msg (Message Nothing $ responseError $ RequestError "invalid request" $ fromUtf8 req') em- Right m -> void $ liftIO $ forkIO $ withSession s $ Log.scope (T.pack name) $ Log.scope "req" $- Log.scope (T.pack $ fromMaybe "_" (view (msg . messageId) m)) $ do- resp' <- flip (traverseOf (msg . message)) m $ \(Request c cdir noFile tm silent) -> do- let- onNotify n- | silent = return ()- | otherwise = traverseOf (msg . message) (const $ mmap' noFile (Response $ Left n)) m >>= answer- Log.sendLog Log.Trace $ "requested >> {}" ~~ fromUtf8 (encode c)- resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ hsdevLiftIO $ withSession s $- processRequest- CommandOptions {- commandOptionsRoot = cdir,- commandOptionsNotify = withSession s . onNotify,- commandOptionsLink = void (swapMVar linkVar exit),- commandOptionsHold = forever (F.getChan rchan) }- c- mmap' noFile resp- answer resp'- where- handleTimeout :: Int -> IO Result -> IO Result- handleTimeout 0 = id- handleTimeout tm = fmap (fromMaybe $ Error $ OtherError "timeout") . timeout tm-- mmap' :: SessionMonad m => Bool -> Response -> m Response-#if mingw32_HOST_OS- mmap' False r = do- mpool <- askSession sessionMmapPool- case mpool of- Just pool -> liftIO $ mmap pool r- Nothing -> return r-#endif- mmap' _ r = return r-- -- Call on disconnected, either no action or exit command- disconnected :: SessionMonad m => MVar (IO ()) -> m ()- disconnected var = do- Log.sendLog Log.Info "disconnected"- liftIO $ join $ takeMVar var---- | Process client by socket-processClientSocket :: SessionMonad m => String -> Socket -> m ()-processClientSocket name s = do- recvChan <- liftIO F.newChan- liftIO $ void $ forkIO $ finally- (Net.getContents s >>= mapM_ (F.putChan recvChan) . L.lines)- (F.closeChan recvChan)- processClient name recvChan (sendLine s)- where- -- NOTE: Network version of `sendAll` goes to infinite loop on client socket close- -- when server's send is blocked, see https://github.com/haskell/network/issues/155- -- After that issue fixed we may revert to `processClientHandle`- sendLine :: Socket -> ByteString -> IO ()- sendLine sock bs = sendAll sock $ L.toStrict $ L.snoc bs '\n'- sendAll :: Socket -> BS.ByteString -> IO ()- sendAll sock bs- | BS.null bs = return ()- | otherwise = do- sent <- Net.send sock bs- when (sent > 0) $ sendAll sock (BS.drop sent bs)--#if mingw32_HOST_OS-data MmapFile = MmapFile String--instance ToJSON MmapFile where- toJSON (MmapFile f) = object ["file" .= f]--instance FromJSON MmapFile where- parseJSON = withObject "file" $ \v -> MmapFile <$> v .:: "file"---- | Push message to mmap and return response which points to this mmap-mmap :: Pool -> Response -> IO Response-mmap mmapPool r- | L.length msg' <= 1024 = return r- | otherwise = do- rvar <- newEmptyMVar- _ <- forkIO $ flip finally (tryPutMVar rvar r) $ void $ withName mmapPool $ \mmapName -> runExceptT $ catchError- (withMapFile mmapName (L.toStrict msg') $ liftIO $ do- _ <- tryPutMVar rvar $ result $ MmapFile mmapName- -- give 10 seconds for client to read data- threadDelay 10000000)- (\_ -> liftIO $ void $ tryPutMVar rvar r)- takeMVar rvar- where- msg' = encode r-#endif---- | If response points to mmap, get its contents and parse-unMmap :: Response -> IO Response-#if mingw32_HOST_OS-unMmap (Response (Right (Result v)))- | Just (MmapFile f) <- parseMaybe parseJSON v = do- cts <- runExceptT (fmap L.fromStrict (readMapFile f))- case cts of- Left _ -> return $ responseError $ ResponseError "can't read map view of file" f- Right r' -> case eitherDecode r' of- Left e' -> return $ responseError $ ResponseError ("can't parse response: {}" ~~ e') (fromUtf8 r')- Right r'' -> return r''-#endif-unMmap r = return r--makeSocket :: ConnectionPort -> IO Socket-makeSocket (NetworkPort _) = socket AF_INET Stream defaultProtocol-makeSocket (UnixPort _) = socket AF_UNIX Stream defaultProtocol--sockAddr :: ConnectionPort -> HostAddress -> SockAddr-sockAddr (NetworkPort p) addr = SockAddrInet (fromIntegral p) addr-sockAddr (UnixPort s) _ = SockAddrUnix s--unlink :: ConnectionPort -> IO ()-unlink (NetworkPort _) = return ()-#if mingw32_HOST_OS-unlink (UnixPort _) = return ()-#else-unlink (UnixPort s) = removeLink s-#endif+{-# LANGUAGE OverloadedStrings, CPP, LambdaCase, TemplateHaskell #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Server.Commands ( + ServerCommand(..), ServerOpts(..), ClientOpts(..), + Request(..), + Msg, isLisp, msg, jsonMsg, lispMsg, encodeMessage, decodeMessage, + sendCommand, runServerCommand, + findPath, + processRequest, processClient, processClientSocket, + module HsDev.Server.Types + ) where + +import Control.Concurrent.Async +import Control.Lens (set, view) +import Control.Monad +import Control.Monad.Catch (bracket, bracket_) +import Data.Aeson hiding (Result, Error) +import qualified Data.Aeson as A +import Data.Aeson.Encode.Pretty +import qualified Data.ByteString.Lazy.Char8 as L +import Data.Maybe +import Network.Socket hiding (connect) +import qualified Network.Socket as Net hiding (send) +import System.Directory +import System.Exit +import System.IO +import qualified System.Log.Simple as Log + +import Text.Format ((~~), (~%)) +import Text.Format.Colored (coloredLine) + +import HsDev.Server.Base +import HsDev.Server.Types +import HsDev.Tools.Base (runTool_) +import HsDev.Error +import HsDev.Util +import HsDev.Version + +#if mingw32_HOST_OS +import Data.List +import System.Environment +import System.Win32.PowerShell (escape, quote, quoteDouble) +#else +import Control.Exception (SomeException, handle) +import System.Posix.Process +import System.Posix.Files (removeLink) +import System.Posix.IO +#endif + +sendCommand :: ClientOpts -> Bool -> Command -> (Notification -> IO a) -> IO Result +sendCommand copts noFile c onNotification = do + asyncAct <- async sendReceive + res <- waitCatch asyncAct + case res of + Left e -> return $ Error $ OtherError (show e) + Right r -> return r + where + sendReceive = do + curDir <- getCurrentDirectory + input <- if clientStdin copts + then Just <$> L.getContents + else return $ toUtf8 <$> Nothing -- arg "data" copts + let + parseData :: L.ByteString -> IO Value + parseData cts = case eitherDecode cts of + Left err -> putStrLn ("Invalid data: " ++ err) >> exitFailure + Right v -> return v + _ <- traverse parseData input -- FIXME: Not used! + + s <- makeSocket (clientPort copts) + addr' <- inet_addr "127.0.0.1" + Net.connect s (sockAddr (clientPort copts) addr') + bracket (socketToHandle s ReadWriteMode) hClose $ \h -> do + L.hPutStrLn h $ encode $ Message Nothing $ Request c curDir noFile (clientTimeout copts) (clientSilent copts) + hFlush h + peekResponse h + + peekResponse h = do + resp <- hGetLineBS h + parseResponse h resp + + parseResponse h str = case eitherDecode str of + Left e -> return $ Error $ ResponseError ("can't parse: {}" ~~ e) (fromUtf8 str) + Right (Message _ r) -> do + Response r' <- unMmap r + case r' of + Left n -> onNotification n >> peekResponse h + Right res -> return res + +runServerCommand :: ServerCommand -> IO () +runServerCommand Version = putStrLn $cabalVersion +runServerCommand (Start sopts) = do +#if mingw32_HOST_OS + let + args = "run" : serverOptsArgs sopts + myExe <- getExecutablePath + curDir <- getCurrentDirectory + let + -- one escape for start-process and other for callable process + -- seems, that start-process just concats arguments into one string + -- start-process foo 'bar baz' ⇒ foo bar baz -- not expected + -- start-process foo '"bar baz"' ⇒ foo "bar baz" -- ok + biescape = escape quote . escape quoteDouble + script = "try {{ start-process {process} {args} -WindowStyle Hidden -WorkingDirectory {dir} }} catch {{ $_.Exception, $_.InvocationInfo.Line }}" + ~~ ("process" ~% escape quote myExe) + ~~ ("args" ~% intercalate ", " (map biescape args)) + ~~ ("dir" ~% escape quote curDir) + _ <- runTool_ "powershell" [ + "-NoProfile", + "-Command", + script] + putStrLn $ "Server started at port {}" ~~ serverPort sopts +#else + let + forkError :: SomeException -> IO () + forkError e = putStrLn $ "Failed to start server: {}" ~~ show e + + proxy :: IO () + proxy = do + _ <- createSession + _ <- forkProcess serverAction + exitImmediately ExitSuccess + + serverAction :: IO () + serverAction = do + mapM_ closeFd [stdInput, stdOutput, stdError] + nullFd <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags + mapM_ (dupTo nullFd) [stdInput, stdOutput, stdError] + closeFd nullFd + runServerCommand (Run sopts) + + handle forkError $ do + _ <- forkProcess proxy + putStrLn $ "Server started at port {}" ~~ serverPort sopts +#endif +runServerCommand (Run sopts) = runServer sopts $ bracket_ (setupServer sopts) (shutdownServer sopts) $ return () +runServerCommand (Stop copts) = runServerCommand (Remote copts False Exit) +runServerCommand (Connect copts) = do + curDir <- getCurrentDirectory + s <- makeSocket $ clientPort copts + addr' <- inet_addr "127.0.0.1" + Net.connect s $ sockAddr (clientPort copts) addr' + bracket (socketToHandle s ReadWriteMode) hClose $ \h -> forM_ [(1 :: Integer)..] $ \i -> ignoreIO $ do + input' <- hGetLineBS stdin + case decodeMsg input' of + Left em -> L.putStrLn $ encodeMessage $ set msg (Message Nothing $ responseError $ OtherError "invalid command") em + Right m -> do + L.hPutStrLn h $ encodeMessage $ set msg (Message (Just $ show i) $ Request (view msg m) curDir True (clientTimeout copts) False) m + waitResp h + where + waitResp h = do + resp <- hGetLineBS h + parseResp h resp + + parseResp h str = case decodeMessage str of + Left em -> putStrLn $ "Can't decode response: {}" ~~ view msg em + Right m -> do + Response r' <- unMmap $ view (msg . message) m + putStrLn $ "{id}: {response}" + ~~ ("id" ~% fromMaybe "_" (view (msg . messageId) m)) + ~~ ("response" ~% fromUtf8 (encodeMsg $ set msg (Response r') m)) + case unResponse (view (msg . message) m) of + Left _ -> waitResp h + _ -> return () +runServerCommand (Remote copts noFile c@(Listen _)) = sendCommand copts noFile c printLog >>= noResult where + printLog :: Notification -> IO () + printLog (Notification v) = case fromJSON v of + A.Error _ -> putStrLn "incorrect notification" + A.Success m -> coloredLine . Log.text $ m + noResult :: Result -> IO () + noResult _ = return () +runServerCommand (Remote copts noFile c) = sendCommand copts noFile c printValue >>= printResult where + printValue :: ToJSON a => a -> IO () + printValue = L.putStrLn . encodeValue + printResult :: Result -> IO () + printResult (Result r) = printValue r + printResult e = printValue e + encodeValue :: ToJSON a => a -> L.ByteString + encodeValue = if clientPretty copts then encodePretty else encode
src/HsDev/Server/Message.hs view
@@ -1,115 +1,118 @@-{-# LANGUAGE OverloadedStrings, DeriveFunctor, TypeSynonymInstances, FlexibleInstances, TemplateHaskell #-}--module HsDev.Server.Message (- Message(..), messageId, message,- messagesById,- Notification(..), Result(..), ResultPart(..),- Response(..), isNotification, notification, result, responseError, resultPart,- groupResponses, responsesById- ) where--import Control.Applicative-import Control.Lens (makeLenses)-import Control.Monad (join)-import Data.Aeson hiding (Error, Result)-import Data.Either (lefts, isRight)-import Data.List (unfoldr)--import HsDev.Types-import HsDev.Util ((.::), (.::?), objectUnion)---- | Message with id to link request and response-data Message a = Message {- _messageId :: Maybe String,- _message :: a }- deriving (Eq, Ord, Show, Functor)--makeLenses ''Message--instance ToJSON a => ToJSON (Message a) where- toJSON (Message i m) = object ["id" .= i] `objectUnion` toJSON m--instance FromJSON a => FromJSON (Message a) where- parseJSON = withObject "message" $ \v ->- Message <$> fmap join (v .::? "id") <*> parseJSON (Object v)--instance Foldable Message where- foldMap f (Message _ m) = f m--instance Traversable Message where- traverse f (Message i m) = Message i <$> f m---- | Get messages by id-messagesById :: Maybe String -> [Message a] -> [a]-messagesById i = map _message . filter ((== i) . _messageId)---- | Notification from server-data Notification = Notification Value deriving (Eq, Show)--instance ToJSON Notification where- toJSON (Notification v) = object ["notify" .= v]--instance FromJSON Notification where- parseJSON = withObject "notification" $ \v -> Notification <$> v .:: "notify"---- | Result from server-data Result =- Result Value |- -- ^ Result- Error HsDevError- -- ^ Error- deriving (Show)--instance ToJSON Result where- toJSON (Result r) = object ["result" .= r]- toJSON (Error e) = toJSON e--instance FromJSON Result where- parseJSON j = (withObject "result" (\v -> (Result <$> v .:: "result")) j) <|> (Error <$> parseJSON j)---- | Part of result list, returns via notification-data ResultPart = ResultPart Value--instance ToJSON ResultPart where- toJSON (ResultPart r) = object ["result-part" .= r]--instance FromJSON ResultPart where- parseJSON = withObject "result-part" $ \v -> ResultPart <$> v .:: "result-part"--newtype Response = Response { unResponse :: Either Notification Result } deriving (Show)--isNotification :: Response -> Bool-isNotification = either (const True) (const False) . unResponse--notification :: ToJSON a => a -> Response-notification = Response . Left . Notification . toJSON--result :: ToJSON a => a -> Response-result = Response . Right . Result . toJSON--responseError :: HsDevError -> Response-responseError = Response . Right . Error--resultPart :: ToJSON a => a -> Notification-resultPart = Notification . toJSON . ResultPart . toJSON--instance ToJSON Response where- toJSON (Response (Left n)) = toJSON n- toJSON (Response (Right r)) = toJSON r--instance FromJSON Response where- parseJSON v = Response <$> ((Left <$> parseJSON v) <|> (Right <$> parseJSON v))--groupResponses :: [Response] -> [([Notification], Result)]-groupResponses = unfoldr break' where- break' :: [Response] -> Maybe (([Notification], Result), [Response])- break' [] = Nothing- break' cs = Just ((lefts (map unResponse ns), r), drop 1 cs') where- (ns, cs') = break (isRight . unResponse) cs- r = case cs' of- (Response (Right r') : _) -> r'- [] -> Error $ OtherError "groupResponses: no result"- _ -> error "groupResponses: impossible happened"--responsesById :: Maybe String -> [Message Response] -> [([Notification], Result)]-responsesById i = groupResponses . messagesById i+{-# LANGUAGE OverloadedStrings, DeriveFunctor, TypeSynonymInstances, FlexibleInstances, TemplateHaskell #-} + +module HsDev.Server.Message ( + Message(..), messageId, message, + messagesById, + Notification(..), Result(..), ResultPart(..), + Response(..), isNotification, result, responseError, + groupResponses, + decodeMessage, encodeMessage, + + module HsDev.Server.Message.Lisp + ) where + +import Control.Applicative +import Control.Lens (makeLenses) +import Control.Monad (join) +import Data.Aeson hiding (Error, Result) +import Data.Either (lefts, isRight) +import Data.List (unfoldr) +import Data.ByteString.Lazy.Char8 (ByteString) + +import HsDev.Types +import HsDev.Util ((.::), (.::?), objectUnion) +import HsDev.Server.Message.Lisp + +-- | Message with id to link request and response +data Message a = Message { + _messageId :: Maybe String, + _message :: a } + deriving (Eq, Ord, Show, Functor) + +makeLenses ''Message + +instance ToJSON a => ToJSON (Message a) where + toJSON (Message i m) = object ["id" .= i] `objectUnion` toJSON m + +instance FromJSON a => FromJSON (Message a) where + parseJSON = withObject "message" $ \v -> + Message <$> fmap join (v .::? "id") <*> parseJSON (Object v) + +instance Foldable Message where + foldMap f (Message _ m) = f m + +instance Traversable Message where + traverse f (Message i m) = Message i <$> f m + +-- | Get messages by id +messagesById :: Maybe String -> [Message a] -> [a] +messagesById i = map _message . filter ((== i) . _messageId) + +-- | Notification from server +data Notification = Notification Value deriving (Eq, Show) + +instance ToJSON Notification where + toJSON (Notification v) = object ["notify" .= v] + +instance FromJSON Notification where + parseJSON = withObject "notification" $ \v -> Notification <$> v .:: "notify" + +-- | Result from server +data Result = + Result Value | + -- ^ Result + Error HsDevError + -- ^ Error + deriving (Show) + +instance ToJSON Result where + toJSON (Result r) = object ["result" .= r] + toJSON (Error e) = toJSON e + +instance FromJSON Result where + parseJSON j = (withObject "result" (\v -> (Result <$> v .:: "result")) j) <|> (Error <$> parseJSON j) + +-- | Part of result list, returns via notification +data ResultPart = ResultPart Value + +instance ToJSON ResultPart where + toJSON (ResultPart r) = object ["result-part" .= r] + +instance FromJSON ResultPart where + parseJSON = withObject "result-part" $ \v -> ResultPart <$> v .:: "result-part" + +newtype Response = Response { unResponse :: Either Notification Result } deriving (Show) + +isNotification :: Response -> Bool +isNotification = either (const True) (const False) . unResponse + +result :: ToJSON a => a -> Response +result = Response . Right . Result . toJSON + +responseError :: HsDevError -> Response +responseError = Response . Right . Error + +instance ToJSON Response where + toJSON (Response (Left n)) = toJSON n + toJSON (Response (Right r)) = toJSON r + +instance FromJSON Response where + parseJSON v = Response <$> ((Left <$> parseJSON v) <|> (Right <$> parseJSON v)) + +groupResponses :: [Response] -> [([Notification], Result)] +groupResponses = unfoldr break' where + break' :: [Response] -> Maybe (([Notification], Result), [Response]) + break' [] = Nothing + break' cs = Just ((lefts (map unResponse ns), r), drop 1 cs') where + (ns, cs') = break (isRight . unResponse) cs + r = case cs' of + (Response (Right r') : _) -> r' + [] -> Error $ OtherError "groupResponses: no result" + _ -> error "groupResponses: impossible happened" + +-- | Decode lisp or json request +decodeMessage :: FromJSON a => ByteString -> Either (Msg String) (Msg (Message a)) +decodeMessage = decodeMsg + +encodeMessage :: ToJSON a => Msg (Message a) -> ByteString +encodeMessage = encodeMsg
+ src/HsDev/Server/Message/Lisp.hs view
@@ -0,0 +1,47 @@+module HsDev.Server.Message.Lisp ( + Msg, + isLisp, msg, + jsonMsg, lispMsg, + + decodeMsg, encodeMsg + ) where + +import Control.Applicative +import Control.Lens +import Control.Monad +import Data.Aeson +import Data.Maybe +import Data.ByteString.Lazy.Char8 (ByteString) + +import Data.Lisp + +type Msg a = (Bool, a) + +isLisp :: Lens' (Msg a) Bool +isLisp = _1 + +msg :: Lens (Msg a) (Msg b) a b +msg = _2 + +jsonMsg :: a -> Msg a +jsonMsg = (,) False + +lispMsg :: a -> Msg a +lispMsg = (,) True + +-- | Decode lisp or json +decodeMsg :: FromJSON a => ByteString -> Either (Msg String) (Msg a) +decodeMsg bstr = over _Left decodeType' decodeMsg' where + decodeType' + | isLisp' = lispMsg + | otherwise = jsonMsg + decodeMsg' = (lispMsg <$> decodeLisp bstr) <|> (jsonMsg <$> eitherDecode bstr) + isLisp' = fromMaybe False $ mplus (try' eitherDecode False) (try' decodeLisp True) + try' :: (ByteString -> Either String Value) -> Bool -> Maybe Bool + try' f l = either (const Nothing) (const $ Just l) $ f bstr + +-- | Encode lisp or json +encodeMsg :: ToJSON a => Msg a -> ByteString +encodeMsg m + | view isLisp m = encodeLisp $ view msg m + | otherwise = encode $ view msg m
src/HsDev/Server/Types.hs view
@@ -1,792 +1,813 @@-{-# LANGUAGE OverloadedStrings, CPP, TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, FlexibleContexts, UndecidableInstances, MultiParamTypeClasses, TypeFamilies, ConstraintKinds #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Server.Types (- ServerMonadBase,- SessionLog(..), Session(..), SessionMonad(..), askSession, ServerM(..),- CommandOptions(..), CommandMonad(..), askOptions, ClientM(..),- withSession, serverListen, serverSetLogLevel, serverWait, serverUpdateDB, serverWriteCache, serverReadCache, inSessionGhc, serverExit, commandRoot, commandNotify, commandLink, commandHold,- ServerCommand(..), ConnectionPort(..), ServerOpts(..), silentOpts, ClientOpts(..), serverOptsArgs, Request(..),-- Command(..), AddedContents(..),- AutoFixCommand(..),- FileSource(..), TargetFilter(..), SearchQuery(..), SearchType(..),- FromCmd(..),- ) where--import Control.Applicative-import Control.Concurrent.Worker-import Control.Lens (each, view, set)-import Control.Monad.Base-import Control.Monad.Catch-import Control.Monad.Except-import Control.Monad.Reader-import Control.Monad.Trans.Control-import Data.Aeson hiding (Result(..), Error)-import qualified Data.Aeson.Types as A-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Default-import Data.Maybe (fromMaybe)-import Data.Monoid-import Data.Foldable (asum)-import Options.Applicative-import System.Log.Simple--import System.Directory.Paths-import Text.Format (Formattable(..))--import HsDev.Database-import qualified HsDev.Database.Async as DB-import HsDev.Error (hsdevError)-import HsDev.Project-import HsDev.Symbols-import HsDev.Server.Message-import HsDev.Watcher.Types (Watcher)-import HsDev.Tools.Ghc.Worker (GhcWorker, GhcM)-import HsDev.Tools.Types (Note, OutputMessage)-import HsDev.Tools.AutoFix (Correction)-import HsDev.Types (HsDevError(..))-import HsDev.Util--#if mingw32_HOST_OS-import System.Win32.FileMapping.NamePool (Pool)-#endif--type ServerMonadBase m = (MonadIO m, MonadMask m, MonadBaseControl IO m, Alternative m, MonadPlus m)--data SessionLog = SessionLog {- sessionLogger :: Log,- sessionListenLog :: IO [String],- sessionLogWait :: IO () }--data Session = Session {- sessionDatabase :: DB.Async Database,- sessionWriteCache :: Database -> ServerM IO (),- sessionReadCache :: (FilePath -> ExceptT String IO Structured) -> ServerM IO (Maybe Database),- sessionLog :: SessionLog,- sessionWatcher :: Watcher,-#if mingw32_HOST_OS- sessionMmapPool :: Maybe Pool,-#endif- sessionGhc :: GhcWorker,- sessionExit :: IO (),- sessionWait :: IO (),- sessionDefines :: [(String, String)] }--class (ServerMonadBase m, MonadLog m) => SessionMonad m where- getSession :: m Session--askSession :: SessionMonad m => (Session -> a) -> m a-askSession f = liftM f getSession--newtype ServerM m a = ServerM { runServerM :: ReaderT Session m a }- deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadReader Session, MonadTrans, MonadThrow, MonadCatch, MonadMask)--instance (MonadIO m, MonadMask m) => MonadLog (ServerM m) where- askLog = ServerM $ asks (sessionLogger . sessionLog)- localLog fn = ServerM . local setLog' . runServerM where- setLog' sess = sess { sessionLog = (sessionLog sess) { sessionLogger = fn (sessionLogger (sessionLog sess)) } }--instance ServerMonadBase m => SessionMonad (ServerM m) where- getSession = ask--instance MonadBase b m => MonadBase b (ServerM m) where- liftBase = ServerM . liftBase--instance MonadBaseControl b m => MonadBaseControl b (ServerM m) where- type StM (ServerM m) a = StM (ReaderT Session m) a- liftBaseWith f = ServerM $ liftBaseWith (\f' -> f (f' . runServerM))- restoreM = ServerM . restoreM--data CommandOptions = CommandOptions {- commandOptionsRoot :: FilePath,- commandOptionsNotify :: Notification -> IO (),- commandOptionsLink :: IO (),- commandOptionsHold :: IO () }--instance Default CommandOptions where- def = CommandOptions "." (const $ return ()) (return ()) (return ())--class (SessionMonad m, MonadPlus m) => CommandMonad m where- getOptions :: m CommandOptions--askOptions :: CommandMonad m => (CommandOptions -> a) -> m a-askOptions f = liftM f getOptions--newtype ClientM m a = ClientM { runClientM :: ServerM (ReaderT CommandOptions m) a }- deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadMask)--instance MonadTrans ClientM where- lift = ClientM . lift . lift--instance (MonadIO m, MonadMask m) => MonadLog (ClientM m) where- askLog = ClientM askLog- localLog fn = ClientM . localLog fn . runClientM--instance ServerMonadBase m => SessionMonad (ClientM m) where- getSession = ClientM getSession--instance ServerMonadBase m => CommandMonad (ClientM m) where- getOptions = ClientM $ lift ask--instance MonadBase b m => MonadBase b (ClientM m) where- liftBase = ClientM . liftBase--instance MonadBaseControl b m => MonadBaseControl b (ClientM m) where- type StM (ClientM m) a = StM (ServerM (ReaderT CommandOptions m)) a- liftBaseWith f = ClientM $ liftBaseWith (\f' -> f (f' . runClientM))- restoreM = ClientM . restoreM---- | Run action on session-withSession :: Session -> ServerM m a -> m a-withSession s act = runReaderT (runServerM act) s---- | Listen server's log-serverListen :: SessionMonad m => m [String]-serverListen = join . liftM liftIO $ askSession (sessionListenLog . sessionLog)---- | Set server's log config-serverSetLogLevel :: SessionMonad m => Level -> m Level-serverSetLogLevel lev = do- l <- askSession (sessionLogger . sessionLog)- cfg <- updateLogConfig l (set (componentCfg "") (Just lev))- return $ fromMaybe def $ view (componentCfg "") cfg---- | Wait for server-serverWait :: SessionMonad m => m ()-serverWait = join . liftM liftIO $ askSession sessionWait---- | Update database-serverUpdateDB :: SessionMonad m => Database -> m ()-serverUpdateDB db = askSession sessionDatabase >>= (`DB.update` return db)---- | Server write cache-serverWriteCache :: SessionMonad m => Database -> m ()-serverWriteCache db = do- s <- getSession- write' <- askSession sessionWriteCache- liftIO $ withSession s $ write' db---- | Server read cache-serverReadCache :: SessionMonad m => (FilePath -> ExceptT String IO Structured) -> m (Maybe Database)-serverReadCache act = do- s <- getSession- read' <- askSession sessionReadCache- liftIO $ withSession s $ read' act---- | In ghc session-inSessionGhc :: SessionMonad m => GhcM a -> m a-inSessionGhc act = do- ghcw <- askSession sessionGhc- inWorkerWith (hsdevError . GhcError . displayException) ghcw act---- | Exit session-serverExit :: SessionMonad m => m ()-serverExit = join . liftM liftIO $ askSession sessionExit--commandRoot :: CommandMonad m => m FilePath-commandRoot = askOptions commandOptionsRoot--commandNotify :: CommandMonad m => Notification -> m ()-commandNotify n = join . liftM liftIO $ askOptions commandOptionsNotify <*> pure n--commandLink :: CommandMonad m => m ()-commandLink = join . liftM liftIO $ askOptions commandOptionsLink--commandHold :: CommandMonad m => m ()-commandHold = join . liftM liftIO $ askOptions commandOptionsHold---- | Server control command-data ServerCommand =- Version |- Start ServerOpts |- Run ServerOpts |- Stop ClientOpts |- Connect ClientOpts |- Remote ClientOpts Bool Command- deriving (Show)--data ConnectionPort = NetworkPort Int | UnixPort String deriving (Eq, Read)--instance Default ConnectionPort where- def = NetworkPort 4567--instance Show ConnectionPort where- show (NetworkPort p) = show p- show (UnixPort s) = "unix " ++ s--instance Formattable ConnectionPort---- | Server options-data ServerOpts = ServerOpts {- serverPort :: ConnectionPort,- serverTimeout :: Int,- serverLog :: Maybe FilePath,- serverLogLevel :: String,- serverCache :: Maybe FilePath,- serverLoad :: Bool,- serverSilent :: Bool }- deriving (Show)--instance Default ServerOpts where- def = ServerOpts def 0 Nothing "info" Nothing False False---- | Silent server with no connection, useful for ghci-silentOpts :: ServerOpts-silentOpts = def { serverSilent = True }---- | Client options-data ClientOpts = ClientOpts {- clientPort :: ConnectionPort,- clientPretty :: Bool,- clientStdin :: Bool,- clientTimeout :: Int,- clientSilent :: Bool }- deriving (Show)--instance Default ClientOpts where- def = ClientOpts def False False 0 False--instance FromCmd ServerCommand where- cmdP = serv <|> remote where- serv = subparser $ mconcat [- cmd "version" "hsdev version" (pure Version),- cmd "start" "start remote server" (Start <$> cmdP),- cmd "run" "run server" (Run <$> cmdP),- cmd "stop" "stop remote server" (Stop <$> cmdP),- cmd "connect" "connect to send commands directly" (Connect <$> cmdP)]- remote = Remote <$> cmdP <*> noFileFlag <*> cmdP--instance FromCmd ServerOpts where- cmdP = ServerOpts <$>- (connectionArg <|> pure (serverPort def)) <*>- (timeoutArg <|> pure (serverTimeout def)) <*>- optional logArg <*>- (logLevelArg <|> pure (serverLogLevel def)) <*>- optional cacheArg <*>- loadFlag <*>- serverSilentFlag--instance FromCmd ClientOpts where- cmdP = ClientOpts <$>- (connectionArg <|> pure (clientPort def)) <*>- prettyFlag <*>- stdinFlag <*>- (timeoutArg <|> pure (clientTimeout def)) <*>- silentFlag--portArg :: Parser ConnectionPort-connectionArg :: Parser ConnectionPort-timeoutArg :: Parser Int-logArg :: Parser FilePath-logLevelArg :: Parser String-cacheArg :: Parser FilePath-noFileFlag :: Parser Bool-loadFlag :: Parser Bool-prettyFlag :: Parser Bool-serverSilentFlag :: Parser Bool-stdinFlag :: Parser Bool-silentFlag :: Parser Bool--portArg = NetworkPort <$> option auto (long "port" <> metavar "number" <> help "connection port")-#if mingw32_HOST_OS-connectionArg = portArg-#else-unixArg :: Parser ConnectionPort-unixArg = UnixPort <$> strOption (long "unix" <> metavar "name" <> help "unix connection port")-connectionArg = portArg <|> unixArg-#endif-timeoutArg = option auto (long "timeout" <> metavar "msec" <> help "query timeout")-logArg = strOption (long "log" <> short 'l' <> metavar "file" <> help "log file")-logLevelArg = strOption (long "log-level" <> metavar "level" <> help "log level: trace/debug/info/warning/error/fatal")-cacheArg = strOption (long "cache" <> metavar "path" <> help "cache directory")-noFileFlag = switch (long "no-file" <> help "don't use mmap files")-loadFlag = switch (long "load" <> help "force load all data from cache on startup")-prettyFlag = switch (long "pretty" <> help "pretty json output")-serverSilentFlag = switch (long "silent" <> help "no stdout/stderr")-stdinFlag = switch (long "stdin" <> help "pass data to stdin")-silentFlag = switch (long "silent" <> help "supress notifications")--serverOptsArgs :: ServerOpts -> [String]-serverOptsArgs sopts = concat [- portArgs (serverPort sopts),- ["--timeout", show $ serverTimeout sopts],- marg "--log" (serverLog sopts),- ["--log-level", serverLogLevel sopts],- marg "--cache" (serverCache sopts),- ["--load" | serverLoad sopts],- ["--silent" | serverSilent sopts]]- where- marg :: String -> Maybe String -> [String]- marg n (Just v) = [n, v]- marg _ _ = []- portArgs :: ConnectionPort -> [String]- portArgs (NetworkPort n) = ["--port", show n]- portArgs (UnixPort s) = ["--unix", s]--data Request = Request {- requestCommand :: Command,- requestDirectory :: FilePath,- requestNoFile :: Bool,- requestTimeout :: Int,- requestSilent :: Bool }- deriving (Show)--instance ToJSON Request where- toJSON (Request c dir f tm s) = object ["current-directory" .= dir, "no-file" .= f, "timeout" .= tm, "silent" .= s] `objectUnion` toJSON c--instance FromJSON Request where- parseJSON = withObject "request" $ \v -> Request <$>- parseJSON (Object v) <*>- ((v .:: "current-directory") <|> pure ".") <*>- ((v .:: "no-file") <|> pure False) <*>- ((v .:: "timeout") <|> pure 0) <*>- ((v .:: "silent") <|> pure False)---- | Command from client-data Command =- Ping |- Listen (Maybe String) |- SetLogLevel String |- AddData { addedContents :: [AddedContents] } |- Scan {- scanProjects :: [FilePath],- scanCabal :: Bool,- scanSandboxes :: [FilePath],- scanFiles :: [FileSource],- scanPaths :: [FilePath],- scanGhcOpts :: [String],- scanDocs :: Bool,- scanInferTypes :: Bool } |- RefineDocs {- docsProjects :: [FilePath],- docsFiles :: [FilePath],- docsModules :: [String] } |- InferTypes {- inferProjects :: [FilePath],- inferFiles :: [FilePath],- inferModules :: [String] } |- Remove {- removeProjects :: [FilePath],- removeCabal :: Bool,- removeSandboxes :: [FilePath],- removeFiles :: [FilePath] } |- RemoveAll |- InfoModules [TargetFilter] |- InfoPackages |- InfoProjects |- InfoSandboxes |- InfoSymbol SearchQuery [TargetFilter] Bool |- InfoModule SearchQuery [TargetFilter] |- InfoResolve FilePath Bool |- InfoProject (Either String FilePath) |- InfoSandbox FilePath |- Lookup String FilePath |- Whois String FilePath |- ResolveScopeModules SearchQuery FilePath |- ResolveScope SearchQuery Bool FilePath |- Complete String Bool FilePath |- Hayoo {- hayooQuery :: String,- hayooPage :: Int,- hayooPages :: Int } |- CabalList { cabalListPackages :: [String] } |- Lint {- lintFiles :: [FileSource] } |- Check {- checkFiles :: [FileSource],- checkGhcOpts :: [String] } |- CheckLint {- checkLintFiles :: [FileSource],- checkLintGhcOpts :: [String] } |- Types {- typesFiles :: [FileSource],- typesGhcOpts :: [String] } |- AutoFix { autoFixCommand :: AutoFixCommand } |- GhcEval { ghcEvalExpressions :: [String], ghcEvalSource :: Maybe FileSource } |- Langs |- Flags |- Link { linkHold :: Bool } |- Exit- deriving (Show)--data AddedContents =- AddedDatabase Database |- AddedModule InspectedModule |- AddedProject Project--instance Show AddedContents where- show = L.unpack . encode--data AutoFixCommand =- AutoFixShow [Note OutputMessage] |- AutoFixFix [Note Correction] [Note Correction] Bool- deriving (Show)--data FileSource = FileSource { fileSource :: FilePath, fileContents :: Maybe String } deriving (Show)-data TargetFilter =- TargetProject String |- TargetFile FilePath |- TargetModule String |- TargetDepsOf String |- TargetPackageDb PackageDb |- TargetCabal |- TargetSandbox FilePath |- TargetPackage String |- TargetSourced |- TargetStandalone- deriving (Eq, Show)-data SearchQuery = SearchQuery String SearchType deriving (Show)-data SearchType = SearchExact | SearchPrefix | SearchInfix | SearchSuffix | SearchRegex deriving (Show)--instance Paths Command where- paths f (Scan projs c cs fs ps ghcs docs infer) = Scan <$>- each f projs <*>- pure c <*>- (each . paths) f cs <*>- (each . paths) f fs <*>- each f ps <*>- pure ghcs <*>- pure docs <*>- pure infer- paths f (RefineDocs projs fs ms) = RefineDocs <$> each f projs <*> each f fs <*> pure ms- paths f (InferTypes projs fs ms) = InferTypes <$> each f projs <*> each f fs <*> pure ms- paths f (Remove projs c cs fs) = Remove <$> each f projs <*> pure c <*> (each . paths) f cs <*> each f fs- paths _ RemoveAll = pure RemoveAll- paths f (InfoModules t) = InfoModules <$> paths f t- paths f (InfoSymbol q t l) = InfoSymbol <$> pure q <*> paths f t <*> pure l- paths f (InfoModule q t) = InfoModule <$> pure q <*> paths f t- paths f (InfoResolve fpath es) = InfoResolve <$> f fpath <*> pure es- paths f (InfoProject (Right proj)) = InfoProject <$> (Right <$> f proj)- paths f (InfoSandbox fpath) = InfoSandbox <$> f fpath- paths f (Lookup n fpath) = Lookup <$> pure n <*> f fpath- paths f (Whois n fpath) = Whois <$> pure n <*> f fpath- paths f (ResolveScopeModules q fpath) = ResolveScopeModules q <$> f fpath- paths f (ResolveScope q g fpath) = ResolveScope q g <$> f fpath- paths f (Complete n g fpath) = Complete n g <$> f fpath- paths f (Lint fs) = Lint <$> (each . paths) f fs- paths f (Check fs ghcs) = Check <$> (each . paths) f fs <*> pure ghcs- paths f (CheckLint fs ghcs) = CheckLint <$> (each . paths) f fs <*> pure ghcs- paths f (Types fs ghcs) = Types <$> (each . paths) f fs <*> pure ghcs- paths f (GhcEval e mf) = GhcEval e <$> traverse (paths f) mf- paths _ c = pure c--instance Paths FileSource where- paths f (FileSource fpath mcts) = FileSource <$> f fpath <*> pure mcts--instance Paths TargetFilter where- paths f (TargetFile fpath) = TargetFile <$> f fpath- paths f (TargetPackageDb pdb) = TargetPackageDb <$> paths f pdb- paths f (TargetSandbox c) = TargetSandbox <$> paths f c- paths _ t = pure t--instance Paths [TargetFilter] where- paths = each . paths--instance FromCmd Command where- cmdP = subparser $ mconcat [- cmd "ping" "ping server" (pure Ping),- cmd "listen" "listen server log" (Listen <$> optional logLevelArg),- cmd "set-log" "set log level" (SetLogLevel <$> strArgument idm),- cmd "add" "add info to database" (AddData <$> option readJSON idm),- cmd "scan" "scan sources" $ Scan <$>- many projectArg <*>- cabalFlag <*>- many sandboxArg <*>- many cmdP <*>- many (pathArg $ help "path") <*>- ghcOpts <*>- docsFlag <*>- inferFlag,- cmd "docs" "scan docs" $ RefineDocs <$> many projectArg <*> many fileArg <*> many moduleArg,- cmd "infer" "infer types" $ InferTypes <$> many projectArg <*> many fileArg <*> many moduleArg,- cmd "remove" "remove modules info" $ Remove <$>- many projectArg <*>- cabalFlag <*>- many sandboxArg <*>- many fileArg,- cmd "remove-all" "remove all data" (pure RemoveAll),- cmd "modules" "list modules" (InfoModules <$> many cmdP),- cmd "packages" "list packages" (pure InfoPackages),- cmd "projects" "list projects" (pure InfoProjects),- cmd "sandboxes" "list sandboxes" (pure InfoSandboxes),- cmd "symbol" "get symbol info" (InfoSymbol <$> cmdP <*> many cmdP <*> localsFlag),- cmd "module" "get module info" (InfoModule <$> cmdP <*> many cmdP),- cmd "resolve" "resolve module scope (or exports)" (InfoResolve <$> fileArg <*> exportsFlag),- cmd "project" "get project info" (InfoProject <$> ((Left <$> projectArg) <|> (Right <$> pathArg idm))),- cmd "sandbox" "get sandbox info" (InfoSandbox <$> pathArg (help "locate sandbox in parent of this path")),- cmd "lookup" "lookup for symbol" (Lookup <$> strArgument idm <*> ctx),- cmd "whois" "get info for symbol" (Whois <$> strArgument idm <*> ctx),- cmd "scope" "get declarations accessible from module or within a project" (- subparser (cmd "modules" "get modules accessible from module or within a project" (ResolveScopeModules <$> cmdP <*> ctx)) <|>- ResolveScope <$> cmdP <*> globalFlag <*> ctx),- cmd "complete" "show completions for input" (Complete <$> strArgument idm <*> wideFlag <*> ctx),- cmd "hayoo" "find declarations online via Hayoo" (Hayoo <$> strArgument idm <*> hayooPageArg <*> hayooPagesArg),- cmd "cabal" "cabal commands" (subparser $ cmd "list" "list cabal packages" (CabalList <$> many (strArgument idm))),- cmd "lint" "lint source files or file contents" (Lint <$> many cmdP),- cmd "check" "check source files or file contents" (Check <$> many cmdP <*> ghcOpts),- cmd "check-lint" "check and lint source files or file contents" (CheckLint <$> many cmdP <*> ghcOpts),- cmd "types" "get types for file expressions" (Types <$> many cmdP <*> ghcOpts),- cmd "autofix" "autofix commands" (AutoFix <$> cmdP),- cmd "ghc" "ghc commands" (subparser $ cmd "eval" "evaluate expression" (GhcEval <$> many (strArgument idm) <*> optional cmdP)),- cmd "langs" "ghc language options" (pure Langs),- cmd "flags" "ghc flags" (pure Flags),- cmd "link" "link to server" (Link <$> holdFlag),- cmd "exit" "exit" (pure Exit)]--instance FromCmd AutoFixCommand where- cmdP = subparser $ mconcat [- cmd "show" "generate corrections for check & lint messages" (AutoFixShow <$> option readJSON (long "data" <> metavar "message" <> help "messages to make fixes for")),- cmd "fix" "fix errors and return rest corrections with updated regions" (AutoFixFix <$>- option readJSON (long "data" <> metavar "message" <> help "messages to fix") <*>- option readJSON (long "rest" <> metavar "correction" <> short 'r' <> help "update corrections") <*>- pureFlag)]--instance FromCmd FileSource where- cmdP = option readJSON (long "contents") <|> (FileSource <$> fileArg <*> pure Nothing)--instance FromCmd TargetFilter where- cmdP = asum [- TargetProject <$> projectArg,- TargetFile <$> fileArg,- TargetModule <$> moduleArg,- TargetDepsOf <$> depsArg,- TargetPackageDb <$> packageDbArg,- flag' TargetCabal (long "cabal"),- TargetSandbox <$> sandboxArg,- TargetPackage <$> packageArg,- flag' TargetSourced (long "src"),- flag' TargetStandalone (long "stand")]--instance FromCmd SearchQuery where- cmdP = SearchQuery <$> (strArgument idm <|> pure "") <*> asum [- flag' SearchExact (long "exact"),- flag' SearchRegex (long "regex"),- flag' SearchInfix (long "infix"),- flag' SearchSuffix (long "suffix"),- pure SearchPrefix <* switch (long "prefix")]--readJSON :: FromJSON a => ReadM a-readJSON = str >>= maybe (readerError "Can't parse JSON argument") return . decode . L.pack--cabalFlag :: Parser Bool-ctx :: Parser FilePath-depsArg :: Parser String-docsFlag :: Parser Bool-exportsFlag :: Parser Bool-fileArg :: Parser FilePath-ghcOpts :: Parser [String]-globalFlag :: Parser Bool-hayooPageArg :: Parser Int-hayooPagesArg :: Parser Int-holdFlag :: Parser Bool-inferFlag :: Parser Bool-localsFlag :: Parser Bool-moduleArg :: Parser String-packageDbArg :: Parser PackageDb-packageArg :: Parser String-pathArg :: Mod OptionFields String -> Parser FilePath-projectArg :: Parser String-pureFlag :: Parser Bool-sandboxArg :: Parser FilePath-wideFlag :: Parser Bool--cabalFlag = switch (long "cabal")-ctx = fileArg-depsArg = strOption (long "deps" <> metavar "object" <> help "filter to such that in dependency of specified object (file or project)")-docsFlag = switch (long "docs" <> help "scan source file docs")-exportsFlag = switch (long "exports" <> short 'e' <> help "resolve module exports")-fileArg = strOption (long "file" <> metavar "path" <> short 'f')-ghcOpts = many (strOption (long "ghc" <> metavar "option" <> short 'g' <> help "options to pass to GHC"))-globalFlag = switch (long "global" <> help "scope of project")-hayooPageArg = option auto (long "page" <> metavar "n" <> short 'p' <> help "page number (0 by default)" <> value 0)-hayooPagesArg = option auto (long "pages" <> metavar "count" <> short 'n' <> help "pages count (1 by default)" <> value 1)-holdFlag = switch (long "hold" <> short 'h' <> help "don't return any response")-inferFlag = switch (long "infer" <> help "infer types")-localsFlag = switch (long "locals" <> short 'l' <> help "look in local declarations")-moduleArg = strOption (long "module" <> metavar "name" <> short 'm' <> help "module name")-packageArg = strOption (long "package" <> metavar "name" <> help "module package")-packageDbArg =- flag' GlobalDb (long "global-db" <> help "global package-db") <|>- flag' UserDb (long "user-db" <> help "user package-db") <|>- (PackageDb <$> strOption (long "package-db" <> metavar "path" <> help "custom package-db"))-pathArg f = strOption (long "path" <> metavar "path" <> short 'p' <> f)-projectArg = strOption (long "project" <> long "proj" <> metavar "project")-pureFlag = switch (long "pure" <> help "don't modify actual file, just return result")-sandboxArg = strOption (long "sandbox" <> metavar "path" <> help "path to cabal sandbox")-wideFlag = switch (long "wide" <> short 'w' <> help "wide mode - complete as if there were no import lists")--instance ToJSON Command where- toJSON Ping = cmdJson "ping" []- toJSON (Listen lev) = cmdJson "listen" ["level" .= lev]- toJSON (SetLogLevel lev) = cmdJson "set-log" ["level" .= lev]- toJSON (AddData cts) = cmdJson "add" ["data" .= cts]- toJSON (Scan projs cabal sboxes fs ps ghcs docs' infer') = cmdJson "scan" [- "projects" .= projs,- "cabal" .= cabal,- "sandboxes" .= sboxes,- "files" .= fs,- "paths" .= ps,- "ghc-opts" .= ghcs,- "docs" .= docs',- "infer" .= infer']- toJSON (RefineDocs projs fs ms) = cmdJson "docs" ["projects" .= projs, "files" .= fs, "modules" .= ms]- toJSON (InferTypes projs fs ms) = cmdJson "infer" ["projects" .= projs, "files" .= fs, "modules" .= ms]- toJSON (Remove projs cabal sboxes fs) = cmdJson "remove" ["projects" .= projs, "cabal" .= cabal, "sandboxes" .= sboxes, "files" .= fs]- toJSON RemoveAll = cmdJson "remove-all" []- toJSON (InfoModules tf) = cmdJson "modules" ["filters" .= tf]- toJSON InfoPackages = cmdJson "packages" []- toJSON InfoProjects = cmdJson "projects" []- toJSON InfoSandboxes = cmdJson "sandboxes" []- toJSON (InfoSymbol q tf l) = cmdJson "symbol" ["query" .= q, "filters" .= tf, "locals" .= l]- toJSON (InfoModule q tf) = cmdJson "module" ["query" .= q, "filters" .= tf]- toJSON (InfoResolve f es) = cmdJson "resolve" ["file" .= f, "exports" .= es]- toJSON (InfoProject p) = cmdJson "project" $ either (\pname -> ["name" .= pname]) (\ppath -> ["path" .= ppath]) p- toJSON (InfoSandbox p) = cmdJson "sandbox" ["path" .= p]- toJSON (Lookup n f) = cmdJson "lookup" ["name" .= n, "file" .= f]- toJSON (Whois n f) = cmdJson "whois" ["name" .= n, "file" .= f]- toJSON (ResolveScopeModules q f) = cmdJson "scope modules" ["query" .= q, "file" .= f]- toJSON (ResolveScope q g f) = cmdJson "scope" ["query" .= q, "global" .= g, "file" .= f]- toJSON (Complete q w f) = cmdJson "complete" ["prefix" .= q, "wide" .= w, "file" .= f]- toJSON (Hayoo q p ps) = cmdJson "hayoo" ["query" .= q, "page" .= p, "pages" .= ps]- toJSON (CabalList ps) = cmdJson "cabal list" ["packages" .= ps]- toJSON (Lint fs) = cmdJson "lint" ["files" .= fs]- toJSON (Check fs ghcs) = cmdJson "check" ["files" .= fs, "ghc-opts" .= ghcs]- toJSON (CheckLint fs ghcs) = cmdJson "check-lint" ["files" .= fs, "ghc-opts" .= ghcs]- toJSON (Types fs ghcs) = cmdJson "types" ["files" .= fs, "ghc-opts" .= ghcs]- toJSON (AutoFix acmd) = toJSON acmd- toJSON (GhcEval exprs f) = cmdJson "ghc eval" ["exprs" .= exprs, "file" .= f]- toJSON Langs = cmdJson "langs" []- toJSON Flags = cmdJson "flags" []- toJSON (Link h) = cmdJson "link" ["hold" .= h]- toJSON Exit = cmdJson "exit" []--instance FromJSON Command where- parseJSON = withObject "command" $ \v -> asum [- guardCmd "ping" v *> pure Ping,- guardCmd "listen" v *> (Listen <$> v .::? "level"),- guardCmd "set-log" v *> (SetLogLevel <$> v .:: "level"),- guardCmd "add" v *> (AddData <$> v .:: "data"),- guardCmd "scan" v *> (Scan <$>- v .::?! "projects" <*>- (v .:: "cabal" <|> pure False) <*>- v .::?! "sandboxes" <*>- v .::?! "files" <*>- v .::?! "paths" <*>- v .::?! "ghc-opts" <*>- (v .:: "docs" <|> pure False) <*>- (v .:: "infer" <|> pure False)),- guardCmd "docs" v *> (RefineDocs <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"),- guardCmd "infer" v *> (InferTypes <$> v .::?! "projects" <*> v .::?! "files" <*> v .::?! "modules"),- guardCmd "remove" v *> (Remove <$>- v .::?! "projects" <*>- (v .:: "cabal" <|> pure False) <*>- v .::?! "sandboxes" <*>- v .::?! "files"),- guardCmd "remove-all" v *> pure RemoveAll,- guardCmd "modules" v *> (InfoModules <$> v .::?! "filters"),- guardCmd "packages" v *> pure InfoPackages,- guardCmd "projects" v *> pure InfoProjects,- guardCmd "sandboxes" v *> pure InfoSandboxes,- guardCmd "symbol" v *> (InfoSymbol <$> v .:: "query" <*> v .::?! "filters" <*> (v .:: "locals" <|> pure False)),- guardCmd "module" v *> (InfoModule <$> v .:: "query" <*> v .::?! "filters"),- guardCmd "resolve" v *> (InfoResolve <$> v .:: "file" <*> (v .:: "exports" <|> pure False)),- guardCmd "project" v *> (InfoProject <$> asum [Left <$> v .:: "name", Right <$> v .:: "path"]),- guardCmd "sandbox" v *> (InfoSandbox <$> v .:: "path"),- guardCmd "lookup" v *> (Lookup <$> v .:: "name" <*> v .:: "file"),- guardCmd "whois" v *> (Whois <$> v .:: "name" <*> v .:: "file"),- guardCmd "scope modules" v *> (ResolveScopeModules <$> v .:: "query" <*> v .:: "file"),- guardCmd "scope" v *> (ResolveScope <$> v .:: "query" <*> (v .:: "global" <|> pure False) <*> v .:: "file"),- guardCmd "complete" v *> (Complete <$> v .:: "prefix" <*> (v .:: "wide" <|> pure False) <*> v .:: "file"),- guardCmd "hayoo" v *> (Hayoo <$> v .:: "query" <*> (v .:: "page" <|> pure 0) <*> (v .:: "pages" <|> pure 1)),- guardCmd "cabal list" v *> (CabalList <$> v .::?! "packages"),- guardCmd "lint" v *> (Lint <$> v .::?! "files"),- guardCmd "check" v *> (Check <$> v .::?! "files" <*> v .::?! "ghc-opts"),- guardCmd "check-lint" v *> (CheckLint <$> v .::?! "files" <*> v .::?! "ghc-opts"),- guardCmd "types" v *> (Types <$> v .::?! "files" <*> v .::?! "ghc-opts"),- AutoFix <$> parseJSON (Object v),- guardCmd "ghc eval" v *> (GhcEval <$> v .::?! "exprs" <*> v .::? "file"),- guardCmd "langs" v *> pure Langs,- guardCmd "flags" v *> pure Flags,- guardCmd "link" v *> (Link <$> (v .:: "hold" <|> pure False)),- guardCmd "exit" v *> pure Exit]--instance ToJSON AddedContents where- toJSON (AddedDatabase db) = object ["database" .= db]- toJSON (AddedModule im) = object ["module" .= im]- toJSON (AddedProject p) = object ["project" .= p]--instance FromJSON AddedContents where- parseJSON = withObject "added-contents" $ \v -> asum [- AddedDatabase <$> v .:: "database",- AddedModule <$> v .:: "module",- AddedProject <$> v .:: "project"]--instance ToJSON AutoFixCommand where- toJSON (AutoFixShow ns) = cmdJson "autofix show" ["messages" .= ns]- toJSON (AutoFixFix ns rests pure') = cmdJson "autofix fix" ["messages" .= ns, "rest" .= rests, "pure" .= pure']--instance FromJSON AutoFixCommand where- parseJSON = withObject "auto-fix-command" $ \v -> asum [- guardCmd "autofix show" v *> (AutoFixShow <$> v .:: "messages"),- guardCmd "autofix fix" v *> (AutoFixFix <$> v .:: "messages" <*> v .::?! "rest" <*> (v .:: "pure" <|> pure True))]--instance ToJSON FileSource where- toJSON (FileSource fpath mcts) = object ["file" .= fpath, "contents" .= mcts]--instance FromJSON FileSource where- parseJSON = withObject "file-contents" $ \v -> FileSource <$> v .:: "file" <*> v .::? "contents"--instance ToJSON TargetFilter where- toJSON (TargetProject pname) = object ["project" .= pname]- toJSON (TargetFile fpath) = object ["file" .= fpath]- toJSON (TargetModule mname) = object ["module" .= mname]- toJSON (TargetDepsOf dep) = object ["deps" .= dep]- toJSON (TargetPackageDb pdb) = object ["db" .= pdb]- toJSON TargetCabal = toJSON ("cabal" :: String)- toJSON (TargetSandbox sbox) = object ["sandbox" .= sbox]- toJSON (TargetPackage pname) = object ["package" .= pname]- toJSON TargetSourced = toJSON ("sourced" :: String)- toJSON TargetStandalone = toJSON ("standalone" :: String)--instance FromJSON TargetFilter where- parseJSON j = obj j <|> str' where- obj = withObject "target-filter" $ \v -> asum [- TargetProject <$> v .:: "project",- TargetFile <$> v .:: "file",- TargetModule <$> v .:: "module",- TargetDepsOf <$> v .:: "deps",- TargetPackageDb <$> v .:: "db",- TargetSandbox <$> v .:: "sandbox",- TargetPackage <$> v .:: "package"]- str' = do- s <- parseJSON j :: A.Parser String- case s of- "cabal" -> return TargetCabal- "sourced" -> return TargetSourced- "standalone" -> return TargetStandalone- _ -> empty--instance ToJSON SearchQuery where- toJSON (SearchQuery q st) = object ["input" .= q, "type" .= st]--instance FromJSON SearchQuery where- parseJSON = withObject "search-query" $ \v -> SearchQuery <$> (v .:: "input" <|> pure "") <*> (v .:: "type" <|> pure SearchPrefix)--instance ToJSON SearchType where- toJSON SearchExact = toJSON ("exact" :: String)- toJSON SearchPrefix = toJSON ("prefix" :: String)- toJSON SearchInfix = toJSON ("infix" :: String)- toJSON SearchSuffix = toJSON ("suffix" :: String)- toJSON SearchRegex = toJSON ("regex" :: String)--instance FromJSON SearchType where- parseJSON v = do- str' <- parseJSON v :: A.Parser String- case str' of- "exact" -> return SearchExact- "prefix" -> return SearchPrefix- "infix" -> return SearchInfix- "suffix" -> return SearchInfix- "regex" -> return SearchRegex- _ -> empty+{-# LANGUAGE OverloadedStrings, CPP, TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, FlexibleContexts, UndecidableInstances, MultiParamTypeClasses, TypeFamilies, ConstraintKinds #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Server.Types ( + ServerMonadBase, + SessionLog(..), Session(..), SessionMonad(..), askSession, ServerM(..), + CommandOptions(..), CommandMonad(..), askOptions, ClientM(..), + withSession, serverListen, serverSetLogLevel, serverWait, serverWaitClients, + serverSqlDatabase, openSqlConnection, closeSqlConnection, withSqlConnection, withSqlTransaction, serverSetFileContents, inSessionGhc, inSessionUpdater, serverExit, commandRoot, commandNotify, commandLink, commandHold, + ServerCommand(..), ConnectionPort(..), ServerOpts(..), silentOpts, ClientOpts(..), serverOptsArgs, Request(..), + + Command(..), + FileSource(..), TargetFilter(..), SearchQuery(..), SearchType(..), + FromCmd(..), + ) where + +import Control.Applicative +import qualified Control.Concurrent.FiniteChan as F +import Control.Lens (view, set) +import Control.Monad.Base +import Control.Monad.Catch +import Control.Monad.Except +import Control.Monad.Morph +import Control.Monad.Reader +import Control.Monad.State +import Control.Monad.Writer +import Control.Monad.Trans.Control +import Data.Aeson hiding (Result(..), Error) +import qualified Data.Aeson.Types as A +import qualified Data.ByteString.Lazy.Char8 as L +import Data.Default +import Data.Maybe (fromMaybe) +import Data.Foldable (asum) +import Data.Text (Text) +import Data.String (fromString) +import qualified Database.SQLite.Simple as SQL +import Options.Applicative +import System.Log.Simple as Log + +import Control.Concurrent.Worker +import System.Directory.Paths +import Text.Format (Formattable(..)) + +import HsDev.Error (hsdevError) +import HsDev.Server.Message +import HsDev.Watcher.Types (Watcher) +import HsDev.Tools.Ghc.Worker (GhcWorker, GhcM) +import HsDev.Tools.Types (Note, OutputMessage) +import HsDev.Tools.AutoFix (Refact) +import HsDev.Types (HsDevError(..)) +import HsDev.Util + +#if mingw32_HOST_OS +import System.Win32.FileMapping.NamePool (Pool) +#endif + +type ServerMonadBase m = (MonadIO m, MonadMask m, MonadBaseControl IO m, Alternative m, MonadPlus m) + +data SessionLog = SessionLog { + sessionLogger :: Log, + sessionListenLog :: IO [Log.Message], + sessionLogWait :: IO () } + +data Session = Session { + sessionSqlDatabase :: SQL.Connection, + sessionSqlPath :: String, + sessionLog :: SessionLog, + sessionWatcher :: Watcher, + sessionFileContents :: Path -> Maybe Text -> IO (), +#if mingw32_HOST_OS + sessionMmapPool :: Maybe Pool, +#endif + sessionGhc :: GhcWorker, + sessionUpdater :: Worker (ServerM IO), + sessionExit :: IO (), + sessionWait :: IO (), + sessionClients :: F.Chan (IO ()), + sessionDefines :: [(String, String)] } + +class (ServerMonadBase m, MonadLog m) => SessionMonad m where + getSession :: m Session + localSession :: (Session -> Session) -> m a -> m a + +askSession :: SessionMonad m => (Session -> a) -> m a +askSession f = liftM f getSession + +newtype ServerM m a = ServerM { runServerM :: ReaderT Session m a } + deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadReader Session, MonadTrans, MonadThrow, MonadCatch, MonadMask) + +instance (MonadIO m, MonadMask m) => MonadLog (ServerM m) where + askLog = ServerM $ asks (sessionLogger . sessionLog) + localLog fn = ServerM . local setLog' . runServerM where + setLog' sess = sess { sessionLog = (sessionLog sess) { sessionLogger = fn (sessionLogger (sessionLog sess)) } } + +instance ServerMonadBase m => SessionMonad (ServerM m) where + getSession = ask + localSession = local + +instance MonadBase b m => MonadBase b (ServerM m) where + liftBase = ServerM . liftBase + +instance MonadBaseControl b m => MonadBaseControl b (ServerM m) where + type StM (ServerM m) a = StM (ReaderT Session m) a + liftBaseWith f = ServerM $ liftBaseWith (\f' -> f (f' . runServerM)) + restoreM = ServerM . restoreM + +instance MFunctor ServerM where + hoist fn = ServerM . hoist fn . runServerM + +instance SessionMonad m => SessionMonad (ReaderT r m) where + getSession = lift getSession + localSession = mapReaderT . localSession + +instance (SessionMonad m, Monoid w) => SessionMonad (WriterT w m) where + getSession = lift getSession + localSession = mapWriterT . localSession + +instance SessionMonad m => SessionMonad (StateT s m) where + getSession = lift getSession + localSession = mapStateT . localSession + +data CommandOptions = CommandOptions { + commandOptionsRoot :: FilePath, + commandOptionsNotify :: Notification -> IO (), + commandOptionsLink :: IO (), + commandOptionsHold :: IO () } + +instance Default CommandOptions where + def = CommandOptions "." (const $ return ()) (return ()) (return ()) + +class (SessionMonad m, MonadPlus m) => CommandMonad m where + getOptions :: m CommandOptions + +askOptions :: CommandMonad m => (CommandOptions -> a) -> m a +askOptions f = liftM f getOptions + +newtype ClientM m a = ClientM { runClientM :: ServerM (ReaderT CommandOptions m) a } + deriving (Functor, Applicative, Alternative, Monad, MonadPlus, MonadIO, MonadThrow, MonadCatch, MonadMask) + +instance MonadTrans ClientM where + lift = ClientM . lift . lift + +instance (MonadIO m, MonadMask m) => MonadLog (ClientM m) where + askLog = ClientM askLog + localLog fn = ClientM . localLog fn . runClientM + +instance ServerMonadBase m => SessionMonad (ClientM m) where + getSession = ClientM getSession + localSession fn = ClientM . localSession fn . runClientM + +instance ServerMonadBase m => CommandMonad (ClientM m) where + getOptions = ClientM $ lift ask + +instance MonadBase b m => MonadBase b (ClientM m) where + liftBase = ClientM . liftBase + +instance MonadBaseControl b m => MonadBaseControl b (ClientM m) where + type StM (ClientM m) a = StM (ServerM (ReaderT CommandOptions m)) a + liftBaseWith f = ClientM $ liftBaseWith (\f' -> f (f' . runClientM)) + restoreM = ClientM . restoreM + +instance MFunctor ClientM where + hoist fn = ClientM . hoist (hoist fn) . runClientM + +instance CommandMonad m => CommandMonad (ReaderT r m) where + getOptions = lift getOptions + +instance (CommandMonad m, Monoid w) => CommandMonad (WriterT w m) where + getOptions = lift getOptions + +instance CommandMonad m => CommandMonad (StateT s m) where + getOptions = lift getOptions + +-- | Run action on session +withSession :: Session -> ServerM m a -> m a +withSession s act = runReaderT (runServerM act) s + +-- | Listen server's log +serverListen :: SessionMonad m => m [Log.Message] +serverListen = join . liftM liftIO $ askSession (sessionListenLog . sessionLog) + +-- | Set server's log config +serverSetLogLevel :: SessionMonad m => Level -> m Level +serverSetLogLevel lev = do + l <- askSession (sessionLogger . sessionLog) + cfg <- updateLogConfig l (set (componentCfg "") (Just lev)) + return $ fromMaybe def $ view (componentCfg "") cfg + +-- | Wait for server +serverWait :: SessionMonad m => m () +serverWait = join . liftM liftIO $ askSession sessionWait + +-- | Wait while clients disconnects +serverWaitClients :: SessionMonad m => m () +serverWaitClients = do + clientChan <- askSession sessionClients + liftIO (F.stopChan clientChan) >>= sequence_ . map liftIO + +-- | Get sql connection +serverSqlDatabase :: SessionMonad m => m SQL.Connection +serverSqlDatabase = askSession sessionSqlDatabase + +-- | Open new sql connection +openSqlConnection :: SessionMonad m => m SQL.Connection +openSqlConnection = do + p <- askSession sessionSqlPath + liftIO $ SQL.open p + +-- | Close sql connection +closeSqlConnection :: SessionMonad m => SQL.Connection -> m () +closeSqlConnection = liftIO . SQL.close + +-- | Locally opens new connection, updating @Session@ +withSqlConnection :: SessionMonad m => m a -> m a +withSqlConnection act = bracket openSqlConnection closeSqlConnection $ \conn -> + localSession (\sess -> sess { sessionSqlDatabase = conn }) act + +-- | With sql transaction +withSqlTransaction :: SessionMonad m => ServerM IO a -> m a +withSqlTransaction fn = do + conn <- serverSqlDatabase + sess <- getSession + liftIO $ SQL.withTransaction conn $ withSession sess fn + +-- | Set custom file contents +serverSetFileContents :: SessionMonad m => Path -> Maybe Text -> m () +serverSetFileContents fpath mcts = do + setCts <- askSession sessionFileContents + liftIO $ setCts fpath mcts + +-- | In ghc session +inSessionGhc :: SessionMonad m => GhcM a -> m a +inSessionGhc act = do + ghcw <- askSession sessionGhc + inWorkerWith (hsdevError . GhcError . displayException) ghcw act + +-- | In updater +inSessionUpdater :: SessionMonad m => ServerM IO a -> m a +inSessionUpdater act = do + uw <- askSession sessionUpdater + inWorkerWith (hsdevError . OtherError . displayException) uw act + +-- | Exit session +serverExit :: SessionMonad m => m () +serverExit = join . liftM liftIO $ askSession sessionExit + +commandRoot :: CommandMonad m => m FilePath +commandRoot = askOptions commandOptionsRoot + +commandNotify :: CommandMonad m => Notification -> m () +commandNotify n = join . liftM liftIO $ askOptions commandOptionsNotify <*> pure n + +commandLink :: CommandMonad m => m () +commandLink = join . liftM liftIO $ askOptions commandOptionsLink + +commandHold :: CommandMonad m => m () +commandHold = join . liftM liftIO $ askOptions commandOptionsHold + +-- | Server control command +data ServerCommand = + Version | + Start ServerOpts | + Run ServerOpts | + Stop ClientOpts | + Connect ClientOpts | + Remote ClientOpts Bool Command + deriving (Show) + +data ConnectionPort = NetworkPort Int | UnixPort String deriving (Eq, Read) + +instance Default ConnectionPort where + def = NetworkPort 4567 + +instance Show ConnectionPort where + show (NetworkPort p) = show p + show (UnixPort s) = "unix " ++ s + +instance Formattable ConnectionPort + +-- | Server options +data ServerOpts = ServerOpts { + serverPort :: ConnectionPort, + serverTimeout :: Int, + serverLog :: Maybe FilePath, + serverLogLevel :: String, + serverDbFile :: Maybe FilePath, + serverSilent :: Bool } + deriving (Show) + +instance Default ServerOpts where + def = ServerOpts def 0 Nothing "info" Nothing False + +-- | Silent server with no connection, useful for ghci +silentOpts :: ServerOpts +silentOpts = def { serverSilent = True } + +-- | Client options +data ClientOpts = ClientOpts { + clientPort :: ConnectionPort, + clientPretty :: Bool, + clientStdin :: Bool, + clientTimeout :: Int, + clientSilent :: Bool } + deriving (Show) + +instance Default ClientOpts where + def = ClientOpts def False False 0 False + +instance FromCmd ServerCommand where + cmdP = serv <|> remote where + serv = subparser $ mconcat [ + cmd "version" "hsdev version" (pure Version), + cmd "start" "start remote server" (Start <$> cmdP), + cmd "run" "run server" (Run <$> cmdP), + cmd "stop" "stop remote server" (Stop <$> cmdP), + cmd "connect" "connect to send commands directly" (Connect <$> cmdP)] + remote = Remote <$> cmdP <*> noFileFlag <*> cmdP + +instance FromCmd ServerOpts where + cmdP = ServerOpts <$> + (connectionArg <|> pure (serverPort def)) <*> + (timeoutArg <|> pure (serverTimeout def)) <*> + optional logArg <*> + (logLevelArg <|> pure (serverLogLevel def)) <*> + optional dbFileArg <*> + serverSilentFlag + +instance FromCmd ClientOpts where + cmdP = ClientOpts <$> + (connectionArg <|> pure (clientPort def)) <*> + prettyFlag <*> + stdinFlag <*> + (timeoutArg <|> pure (clientTimeout def)) <*> + silentFlag + +portArg :: Parser ConnectionPort +connectionArg :: Parser ConnectionPort +timeoutArg :: Parser Int +logArg :: Parser FilePath +logLevelArg :: Parser String +noFileFlag :: Parser Bool +prettyFlag :: Parser Bool +serverSilentFlag :: Parser Bool +stdinFlag :: Parser Bool +silentFlag :: Parser Bool +dbFileArg :: Parser FilePath + +portArg = NetworkPort <$> option auto (long "port" <> metavar "number" <> help "connection port") +#if mingw32_HOST_OS +connectionArg = portArg +#else +unixArg :: Parser ConnectionPort +unixArg = UnixPort <$> strOption (long "unix" <> metavar "name" <> help "unix connection port") +connectionArg = portArg <|> unixArg +#endif +timeoutArg = option auto (long "timeout" <> metavar "msec" <> help "query timeout") +logArg = strOption (long "log" <> short 'l' <> metavar "file" <> help "log file") +logLevelArg = strOption (long "log-level" <> metavar "level" <> help "log level: trace/debug/info/warning/error/fatal") +noFileFlag = switch (long "no-file" <> help "don't use mmap files") +prettyFlag = switch (long "pretty" <> help "pretty json output") +serverSilentFlag = switch (long "silent" <> help "no stdout/stderr") +stdinFlag = switch (long "stdin" <> help "pass data to stdin") +silentFlag = switch (long "silent" <> help "supress notifications") +dbFileArg = strOption (long "db" <> metavar "path" <> help "path to sql database") + +serverOptsArgs :: ServerOpts -> [String] +serverOptsArgs sopts = concat [ + portArgs (serverPort sopts), + ["--timeout", show $ serverTimeout sopts], + marg "--log" (serverLog sopts), + ["--log-level", serverLogLevel sopts], + marg "--db" (serverDbFile sopts), + ["--silent" | serverSilent sopts]] + where + marg :: String -> Maybe String -> [String] + marg n (Just v) = [n, v] + marg _ _ = [] + portArgs :: ConnectionPort -> [String] + portArgs (NetworkPort n) = ["--port", show n] + portArgs (UnixPort s) = ["--unix", s] + +data Request = Request { + requestCommand :: Command, + requestDirectory :: FilePath, + requestNoFile :: Bool, + requestTimeout :: Int, + requestSilent :: Bool } + deriving (Show) + +instance ToJSON Request where + toJSON (Request c dir f tm s) = object ["current-directory" .= dir, "no-file" .= f, "timeout" .= tm, "silent" .= s] `objectUnion` toJSON c + +instance FromJSON Request where + parseJSON = withObject "request" $ \v -> Request <$> + parseJSON (Object v) <*> + ((v .:: "current-directory") <|> pure ".") <*> + ((v .:: "no-file") <|> pure False) <*> + ((v .:: "timeout") <|> pure 0) <*> + ((v .:: "silent") <|> pure False) + +-- | Command from client +data Command = + Ping | + Listen (Maybe String) | + SetLogLevel String | + Scan { + scanProjects :: [Path], + scanCabal :: Bool, + scanSandboxes :: [Path], + scanFiles :: [FileSource], + scanPaths :: [Path], + scanGhcOpts :: [String], + scanDocs :: Bool, + scanInferTypes :: Bool } | + SetFileContents Path (Maybe Text) | + RefineDocs { + docsProjects :: [Path], + docsFiles :: [Path] } | + InferTypes { + inferProjects :: [Path], + inferFiles :: [Path] } | + Remove { + removeProjects :: [Path], + removeCabal :: Bool, + removeSandboxes :: [Path], + removeFiles :: [Path] } | + RemoveAll | + InfoPackages | + InfoProjects | + InfoSandboxes | + InfoSymbol SearchQuery [TargetFilter] Bool Bool | + InfoModule SearchQuery [TargetFilter] Bool Bool | + InfoProject (Either Text Path) | + InfoSandbox Path | + Lookup Text Path | + Whois Text Path | + Whoat Int Int Path | + ResolveScopeModules SearchQuery Path | + ResolveScope SearchQuery Path | + FindUsages Text | + Complete Text Bool Path | + Hayoo { + hayooQuery :: String, + hayooPage :: Int, + hayooPages :: Int } | + CabalList { cabalListPackages :: [Text] } | + UnresolvedSymbols { + unresolvedFiles :: [Path] } | + Lint { + lintFiles :: [FileSource] } | + Check { + checkFiles :: [FileSource], + checkGhcOpts :: [String], + checkClear :: Bool } | + CheckLint { + checkLintFiles :: [FileSource], + checkLintGhcOpts :: [String], + checkLinkClear :: Bool } | + Types { + typesFiles :: [FileSource], + typesGhcOpts :: [String], + typesClear :: Bool } | + AutoFix [Note OutputMessage] | + Refactor [Note Refact] [Note Refact] Bool | + Rename Text Text Path | + GhcEval { ghcEvalExpressions :: [String], ghcEvalSource :: Maybe FileSource } | + Langs | + Flags | + Link { linkHold :: Bool } | + StopGhc | + Exit + deriving (Show) + +data FileSource = FileSource { fileSource :: Path, fileContents :: Maybe Text } deriving (Show) +data TargetFilter = + TargetProject Text | + TargetFile Path | + TargetModule Text | + TargetPackage Text | + TargetInstalled | + TargetSourced | + TargetStandalone + deriving (Eq, Show) +data SearchQuery = SearchQuery Text SearchType deriving (Show) +data SearchType = SearchExact | SearchPrefix | SearchInfix | SearchSuffix deriving (Show) + +instance Paths Command where + paths f (Scan projs c cs fs ps ghcs docs infer) = Scan <$> + traverse (paths f) projs <*> + pure c <*> + traverse (paths f) cs <*> + traverse (paths f) fs <*> + traverse (paths f) ps <*> + pure ghcs <*> + pure docs <*> + pure infer + paths f (SetFileContents p cts) = SetFileContents <$> paths f p <*> pure cts + paths f (RefineDocs projs fs) = RefineDocs <$> traverse (paths f) projs <*> traverse (paths f) fs + paths f (InferTypes projs fs) = InferTypes <$> traverse (paths f) projs <*> traverse (paths f) fs + paths f (Remove projs c cs fs) = Remove <$> traverse (paths f) projs <*> pure c <*> traverse (paths f) cs <*> traverse (paths f) fs + paths _ RemoveAll = pure RemoveAll + paths f (InfoSymbol q t h l) = InfoSymbol <$> pure q <*> traverse (paths f) t <*> pure h <*> pure l + paths f (InfoModule q t h i) = InfoModule <$> pure q <*> traverse (paths f) t <*> pure h <*> pure i + paths f (InfoProject (Right proj)) = InfoProject <$> (Right <$> paths f proj) + paths f (InfoSandbox fpath) = InfoSandbox <$> paths f fpath + paths f (Lookup n fpath) = Lookup <$> pure n <*> paths f fpath + paths f (Whois n fpath) = Whois <$> pure n <*> paths f fpath + paths f (Whoat l c fpath) = Whoat <$> pure l <*> pure c <*> paths f fpath + paths f (ResolveScopeModules q fpath) = ResolveScopeModules q <$> paths f fpath + paths f (ResolveScope q fpath) = ResolveScope q <$> paths f fpath + paths _ (FindUsages nm) = pure $ FindUsages nm + paths f (Complete n g fpath) = Complete n g <$> paths f fpath + paths f (UnresolvedSymbols fs) = UnresolvedSymbols <$> traverse (paths f) fs + paths f (Lint fs) = Lint <$> traverse (paths f) fs + paths f (Check fs ghcs c) = Check <$> traverse (paths f) fs <*> pure ghcs <*> pure c + paths f (CheckLint fs ghcs c) = CheckLint <$> traverse (paths f) fs <*> pure ghcs <*> pure c + paths f (Types fs ghcs c) = Types <$> traverse (paths f) fs <*> pure ghcs <*> pure c + paths f (GhcEval e mf) = GhcEval e <$> traverse (paths f) mf + paths _ c = pure c + +instance Paths FileSource where + paths f (FileSource fpath mcts) = FileSource <$> paths f fpath <*> pure mcts + +instance Paths TargetFilter where + paths f (TargetFile fpath) = TargetFile <$> paths f fpath + paths _ t = pure t + +instance FromCmd Command where + cmdP = subparser $ mconcat [ + cmd "ping" "ping server" (pure Ping), + cmd "listen" "listen server log" (Listen <$> optional logLevelArg), + cmd "set-log" "set log level" (SetLogLevel <$> strArgument idm), + cmd "scan" "scan sources" $ Scan <$> + many projectArg <*> + cabalFlag <*> + many sandboxArg <*> + many cmdP <*> + many (pathArg $ help "path") <*> + ghcOpts <*> + docsFlag <*> + inferFlag, + cmd "set-file-contents" "set edited file contents, which will be used instead of contents in file until it updated" $ + SetFileContents <$> fileArg <*> optional contentsArg, + cmd "docs" "scan docs" $ RefineDocs <$> many projectArg <*> many fileArg, + cmd "infer" "infer types" $ InferTypes <$> many projectArg <*> many fileArg, + cmd "remove" "remove modules info" $ Remove <$> + many projectArg <*> + cabalFlag <*> + many sandboxArg <*> + many fileArg, + cmd "remove-all" "remove all data" (pure RemoveAll), + cmd "packages" "list packages" (pure InfoPackages), + cmd "projects" "list projects" (pure InfoProjects), + cmd "sandboxes" "list sandboxes" (pure InfoSandboxes), + cmd "symbol" "get symbol info" (InfoSymbol <$> cmdP <*> many cmdP <*> headerFlag <*> localsFlag), + cmd "module" "get module info" (InfoModule <$> cmdP <*> many cmdP <*> headerFlag <*> inspectionFlag), + cmd "project" "get project info" (InfoProject <$> ((Left <$> projectArg) <|> (Right <$> pathArg idm))), + cmd "sandbox" "get sandbox info" (InfoSandbox <$> pathArg (help "locate sandbox in parent of this path")), + cmd "lookup" "lookup for symbol" (Lookup <$> textArgument idm <*> ctx), + cmd "whois" "get info for symbol" (Whois <$> textArgument idm <*> ctx), + cmd "whoat" "get info for symbol under cursor" (Whoat <$> argument auto (metavar "line") <*> argument auto (metavar "column") <*> ctx), + cmd "scope" "get declarations accessible from module or within a project" ( + subparser (cmd "modules" "get modules accessible from module or within a project" (ResolveScopeModules <$> cmdP <*> ctx)) <|> + ResolveScope <$> cmdP <*> ctx), + cmd "usages" "find usages of fully qualified symbol (qualified with module its defined in)" (FindUsages <$> textArgument idm), + cmd "complete" "show completions for input" (Complete <$> textArgument idm <*> wideFlag <*> ctx), + cmd "hayoo" "find declarations online via Hayoo" (Hayoo <$> strArgument idm <*> hayooPageArg <*> hayooPagesArg), + cmd "cabal" "cabal commands" (subparser $ cmd "list" "list cabal packages" (CabalList <$> many (textArgument idm))), + cmd "unresolveds" "list unresolved symbols in source file" (UnresolvedSymbols <$> many fileArg), + cmd "lint" "lint source files or file contents" (Lint <$> many cmdP), + cmd "check" "check source files or file contents" (Check <$> many cmdP <*> ghcOpts <*> clearFlag), + cmd "check-lint" "check and lint source files or file contents" (CheckLint <$> many cmdP <*> ghcOpts <*> clearFlag), + cmd "types" "get types for file expressions" (Types <$> many cmdP <*> ghcOpts <*> clearFlag), + cmd "autofixes" "get autofixes by output messages" (AutoFix <$> option readJSON (long "data" <> metavar "message" <> help "messages to make fixes for")), + cmd "refactor" "apply some refactors and get rest updated" (Refactor <$> + option readJSON (long "data" <> metavar "message" <> help "messages to fix") <*> + option readJSON (long "rest" <> metavar "correction" <> short 'r' <> help "update corrections") <*> + pureFlag), + cmd "rename" "get rename refactors" (Rename <$> textArgument idm <*> textArgument idm <*> ctx), + cmd "ghc" "ghc commands" (subparser $ cmd "eval" "evaluate expression" (GhcEval <$> many (strArgument idm) <*> optional cmdP)), + cmd "langs" "ghc language options" (pure Langs), + cmd "flags" "ghc flags" (pure Flags), + cmd "link" "link to server" (Link <$> holdFlag), + cmd "stop-ghc" "stop ghc sessions" (pure StopGhc), + cmd "exit" "exit" (pure Exit)] + +instance FromCmd FileSource where + cmdP = option readJSON (long "contents") <|> (FileSource <$> fileArg <*> pure Nothing) + +instance FromCmd TargetFilter where + cmdP = asum [ + TargetProject <$> projectArg, + TargetFile <$> fileArg, + TargetModule <$> moduleArg, + TargetPackage <$> packageArg, + flag' TargetInstalled (long "installed"), + flag' TargetSourced (long "src"), + flag' TargetStandalone (long "stand")] + +instance FromCmd SearchQuery where + cmdP = SearchQuery <$> (textArgument idm <|> pure "") <*> asum [ + flag' SearchExact (long "exact"), + flag' SearchInfix (long "infix"), + flag' SearchSuffix (long "suffix"), + pure SearchPrefix <* switch (long "prefix")] + +readJSON :: FromJSON a => ReadM a +readJSON = str >>= maybe (readerError "Can't parse JSON argument") return . decode . L.pack + +textOption :: Mod OptionFields String -> Parser Text +textOption = fmap fromString . strOption + +textArgument :: Mod ArgumentFields String -> Parser Text +textArgument = fmap fromString . strArgument + +cabalFlag :: Parser Bool +clearFlag :: Parser Bool +contentsArg :: Parser Text +ctx :: Parser Path +docsFlag :: Parser Bool +fileArg :: Parser Path +ghcOpts :: Parser [String] +hayooPageArg :: Parser Int +hayooPagesArg :: Parser Int +headerFlag :: Parser Bool +holdFlag :: Parser Bool +inferFlag :: Parser Bool +inspectionFlag :: Parser Bool +localsFlag :: Parser Bool +moduleArg :: Parser Text +packageArg :: Parser Text +pathArg :: Mod OptionFields String -> Parser Path +projectArg :: Parser Path +pureFlag :: Parser Bool +sandboxArg :: Parser Path +wideFlag :: Parser Bool + +cabalFlag = switch (long "cabal") +clearFlag = switch (long "clear" <> short 'c' <> help "clear run, drop previous state") +contentsArg = textOption (long "contents" <> help "text contents") +ctx = fileArg +docsFlag = switch (long "docs" <> help "scan source file docs") +fileArg = textOption (long "file" <> metavar "path" <> short 'f') +ghcOpts = many (strOption (long "ghc" <> metavar "option" <> short 'g' <> help "options to pass to GHC")) +hayooPageArg = option auto (long "page" <> metavar "n" <> short 'p' <> help "page number (0 by default)" <> value 0) +hayooPagesArg = option auto (long "pages" <> metavar "count" <> short 'n' <> help "pages count (1 by default)" <> value 1) +headerFlag = switch (long "header" <> short 'h' <> help "show only header of module") +holdFlag = switch (long "hold" <> short 'h' <> help "don't return any response") +inferFlag = switch (long "infer" <> help "infer types") +inspectionFlag = switch (long "inspection" <> short 'i' <> help "return inspection data") +localsFlag = switch (long "locals" <> short 'l' <> help "look in local declarations") +moduleArg = textOption (long "module" <> metavar "name" <> short 'm' <> help "module name") +packageArg = textOption (long "package" <> metavar "name" <> help "module package") +pathArg f = textOption (long "path" <> metavar "path" <> short 'p' <> f) +projectArg = textOption (long "project" <> long "proj" <> metavar "project") +pureFlag = switch (long "pure" <> help "don't modify actual file, just return result") +sandboxArg = textOption (long "sandbox" <> metavar "path" <> help "path to cabal sandbox") +wideFlag = switch (long "wide" <> short 'w' <> help "wide mode - complete as if there were no import lists") + +instance ToJSON Command where + toJSON Ping = cmdJson "ping" [] + toJSON (Listen lev) = cmdJson "listen" ["level" .= lev] + toJSON (SetLogLevel lev) = cmdJson "set-log" ["level" .= lev] + toJSON (Scan projs cabal sboxes fs ps ghcs docs' infer') = cmdJson "scan" [ + "projects" .= projs, + "cabal" .= cabal, + "sandboxes" .= sboxes, + "files" .= fs, + "paths" .= ps, + "ghc-opts" .= ghcs, + "docs" .= docs', + "infer" .= infer'] + toJSON (SetFileContents f cts) = cmdJson "set-file-contents" ["file" .= f, "contents" .= cts] + toJSON (RefineDocs projs fs) = cmdJson "docs" ["projects" .= projs, "files" .= fs] + toJSON (InferTypes projs fs) = cmdJson "infer" ["projects" .= projs, "files" .= fs] + toJSON (Remove projs cabal sboxes fs) = cmdJson "remove" ["projects" .= projs, "cabal" .= cabal, "sandboxes" .= sboxes, "files" .= fs] + toJSON RemoveAll = cmdJson "remove-all" [] + toJSON InfoPackages = cmdJson "packages" [] + toJSON InfoProjects = cmdJson "projects" [] + toJSON InfoSandboxes = cmdJson "sandboxes" [] + toJSON (InfoSymbol q tf h l) = cmdJson "symbol" ["query" .= q, "filters" .= tf, "header" .= h, "locals" .= l] + toJSON (InfoModule q tf h i) = cmdJson "module" ["query" .= q, "filters" .= tf, "header" .= h, "inspection" .= i] + toJSON (InfoProject p) = cmdJson "project" $ either (\pname -> ["name" .= pname]) (\ppath -> ["path" .= ppath]) p + toJSON (InfoSandbox p) = cmdJson "sandbox" ["path" .= p] + toJSON (Lookup n f) = cmdJson "lookup" ["name" .= n, "file" .= f] + toJSON (Whois n f) = cmdJson "whois" ["name" .= n, "file" .= f] + toJSON (Whoat l c f) = cmdJson "whoat" ["line" .= l, "column" .= c, "file" .= f] + toJSON (ResolveScopeModules q f) = cmdJson "scope modules" ["query" .= q, "file" .= f] + toJSON (ResolveScope q f) = cmdJson "scope" ["query" .= q, "file" .= f] + toJSON (FindUsages nm) = cmdJson "usages" ["name" .= nm] + toJSON (Complete q w f) = cmdJson "complete" ["prefix" .= q, "wide" .= w, "file" .= f] + toJSON (Hayoo q p ps) = cmdJson "hayoo" ["query" .= q, "page" .= p, "pages" .= ps] + toJSON (CabalList ps) = cmdJson "cabal list" ["packages" .= ps] + toJSON (UnresolvedSymbols fs) = cmdJson "unresolveds" ["files" .= fs] + toJSON (Lint fs) = cmdJson "lint" ["files" .= fs] + toJSON (Check fs ghcs c) = cmdJson "check" ["files" .= fs, "ghc-opts" .= ghcs, "clear" .= c] + toJSON (CheckLint fs ghcs c) = cmdJson "check-lint" ["files" .= fs, "ghc-opts" .= ghcs, "clear" .= c] + toJSON (Types fs ghcs c) = cmdJson "types" ["files" .= fs, "ghc-opts" .= ghcs, "clear" .= c] + toJSON (AutoFix ns) = cmdJson "autofixes" ["messages" .= ns] + toJSON (Refactor ns rests pure') = cmdJson "refactor" ["messages" .= ns, "rest" .= rests, "pure" .= pure'] + toJSON (Rename n n' f) = cmdJson "rename" ["name" .= n, "new-name" .= n', "file" .= f] + toJSON (GhcEval exprs f) = cmdJson "ghc eval" ["exprs" .= exprs, "file" .= f] + toJSON Langs = cmdJson "langs" [] + toJSON Flags = cmdJson "flags" [] + toJSON (Link h) = cmdJson "link" ["hold" .= h] + toJSON StopGhc = cmdJson "stop-ghc" [] + toJSON Exit = cmdJson "exit" [] + +instance FromJSON Command where + parseJSON = withObject "command" $ \v -> asum [ + guardCmd "ping" v *> pure Ping, + guardCmd "listen" v *> (Listen <$> v .::? "level"), + guardCmd "set-log" v *> (SetLogLevel <$> v .:: "level"), + guardCmd "scan" v *> (Scan <$> + v .::?! "projects" <*> + (v .:: "cabal" <|> pure False) <*> + v .::?! "sandboxes" <*> + v .::?! "files" <*> + v .::?! "paths" <*> + v .::?! "ghc-opts" <*> + (v .:: "docs" <|> pure False) <*> + (v .:: "infer" <|> pure False)), + guardCmd "set-file-contents" v *> (SetFileContents <$> v .:: "file" <*> v .:: "contents"), + guardCmd "docs" v *> (RefineDocs <$> v .::?! "projects" <*> v .::?! "files"), + guardCmd "infer" v *> (InferTypes <$> v .::?! "projects" <*> v .::?! "files"), + guardCmd "remove" v *> (Remove <$> + v .::?! "projects" <*> + (v .:: "cabal" <|> pure False) <*> + v .::?! "sandboxes" <*> + v .::?! "files"), + guardCmd "remove-all" v *> pure RemoveAll, + guardCmd "packages" v *> pure InfoPackages, + guardCmd "projects" v *> pure InfoProjects, + guardCmd "sandboxes" v *> pure InfoSandboxes, + guardCmd "symbol" v *> (InfoSymbol <$> v .:: "query" <*> v .::?! "filters" <*> v .:: "header" <*> (v .:: "locals" <|> pure False)), + guardCmd "module" v *> (InfoModule <$> v .:: "query" <*> v .::?! "filters" <*> v .:: "header" <*> v .:: "inspection"), + guardCmd "project" v *> (InfoProject <$> asum [Left <$> v .:: "name", Right <$> v .:: "path"]), + guardCmd "sandbox" v *> (InfoSandbox <$> v .:: "path"), + guardCmd "lookup" v *> (Lookup <$> v .:: "name" <*> v .:: "file"), + guardCmd "whois" v *> (Whois <$> v .:: "name" <*> v .:: "file"), + guardCmd "whoat" v *> (Whoat <$> v .:: "line" <*> v .:: "column" <*> v .:: "file"), + guardCmd "scope modules" v *> (ResolveScopeModules <$> v .:: "query" <*> v .:: "file"), + guardCmd "scope" v *> (ResolveScope <$> v .:: "query" <*> v .:: "file"), + guardCmd "usages" v *> (FindUsages <$> v .:: "name"), + guardCmd "complete" v *> (Complete <$> v .:: "prefix" <*> (v .:: "wide" <|> pure False) <*> v .:: "file"), + guardCmd "hayoo" v *> (Hayoo <$> v .:: "query" <*> (v .:: "page" <|> pure 0) <*> (v .:: "pages" <|> pure 1)), + guardCmd "cabal list" v *> (CabalList <$> v .::?! "packages"), + guardCmd "unresolveds" v *> (UnresolvedSymbols <$> v .::?! "files"), + guardCmd "lint" v *> (Lint <$> v .::?! "files"), + guardCmd "check" v *> (Check <$> v .::?! "files" <*> v .::?! "ghc-opts" <*> (v .:: "clear" <|> pure False)), + guardCmd "check-lint" v *> (CheckLint <$> v .::?! "files" <*> v .::?! "ghc-opts" <*> (v .:: "clear" <|> pure False)), + guardCmd "types" v *> (Types <$> v .::?! "files" <*> v .::?! "ghc-opts" <*> (v .:: "clear" <|> pure False)), + guardCmd "autofixes" v *> (AutoFix <$> v .:: "messages"), + guardCmd "refactor" v *> (Refactor <$> v .:: "messages" <*> v .::?! "rest" <*> (v .:: "pure" <|> pure True)), + guardCmd "rename" v *> (Rename <$> v .:: "name" <*> v .:: "new-name" <*> v .:: "file"), + guardCmd "ghc eval" v *> (GhcEval <$> v .::?! "exprs" <*> v .::? "file"), + guardCmd "langs" v *> pure Langs, + guardCmd "flags" v *> pure Flags, + guardCmd "link" v *> (Link <$> (v .:: "hold" <|> pure False)), + guardCmd "stop-ghc" v *> pure StopGhc, + guardCmd "exit" v *> pure Exit] + +instance ToJSON FileSource where + toJSON (FileSource fpath mcts) = object ["file" .= fpath, "contents" .= mcts] + +instance FromJSON FileSource where + parseJSON = withObject "file-contents" $ \v -> FileSource <$> v .:: "file" <*> v .::? "contents" + +instance ToJSON TargetFilter where + toJSON (TargetProject pname) = object ["project" .= pname] + toJSON (TargetFile fpath) = object ["file" .= fpath] + toJSON (TargetModule mname) = object ["module" .= mname] + toJSON (TargetPackage pname) = object ["package" .= pname] + toJSON TargetInstalled = toJSON ("installed" :: String) + toJSON TargetSourced = toJSON ("sourced" :: String) + toJSON TargetStandalone = toJSON ("standalone" :: String) + +instance FromJSON TargetFilter where + parseJSON j = obj j <|> str' where + obj = withObject "target-filter" $ \v -> asum [ + TargetProject <$> v .:: "project", + TargetFile <$> v .:: "file", + TargetModule <$> v .:: "module", + TargetPackage <$> v .:: "package"] + str' = do + s <- parseJSON j :: A.Parser String + case s of + "installed" -> return TargetInstalled + "sourced" -> return TargetSourced + "standalone" -> return TargetStandalone + _ -> empty + +instance ToJSON SearchQuery where + toJSON (SearchQuery q st) = object ["input" .= q, "type" .= st] + +instance FromJSON SearchQuery where + parseJSON = withObject "search-query" $ \v -> SearchQuery <$> (v .:: "input" <|> pure "") <*> (v .:: "type" <|> pure SearchPrefix) + +instance ToJSON SearchType where + toJSON SearchExact = toJSON ("exact" :: String) + toJSON SearchPrefix = toJSON ("prefix" :: String) + toJSON SearchInfix = toJSON ("infix" :: String) + toJSON SearchSuffix = toJSON ("suffix" :: String) + +instance FromJSON SearchType where + parseJSON v = do + str' <- parseJSON v :: A.Parser String + case str' of + "exact" -> return SearchExact + "prefix" -> return SearchPrefix + "infix" -> return SearchInfix + "suffix" -> return SearchInfix + _ -> empty
src/HsDev/Stack.hs view
@@ -1,131 +1,133 @@-{-# LANGUAGE TemplateHaskell, RankNTypes #-}--module HsDev.Stack (- stack, yaml,- path, pathOf,- build, buildDeps, configure,- StackEnv(..), stackRoot, stackProject, stackConfig, stackGhc, stackSnapshot, stackLocal,- getStackEnv, projectEnv,- stackPackageDbStack,-- stackCompiler, stackArch,-- MaybeT(..)- ) where--import Control.Arrow-import Control.Lens (makeLenses, Lens', at, ix, lens, (^?), (^.))-import Control.Monad-import Control.Monad.Trans.Maybe-import Control.Monad.IO.Class-import Data.Char-import Data.Maybe-import Data.Map (Map)-import qualified Data.Map as M-import Distribution.Compiler-import Distribution.System-import qualified Distribution.Text as T (display)-import System.Directory-import System.Environment-import System.FilePath-import System.Log.Simple (MonadLog(..))--import qualified Packages as GHC--import HsDev.Error-import HsDev.PackageDb-import qualified HsDev.Tools.Ghc.Compat as Compat-import HsDev.Scan.Browse (withPackages)-import HsDev.Util as Util-import HsDev.Tools.Base (runTool_)---- | Get compiler version-stackCompiler :: MonadLog m => m String-stackCompiler = do- res <- withPackages ["-no-user-package-db"] $- return .- map (GHC.packageNameString &&& GHC.packageVersion) .- fromMaybe [] .- Compat.pkgDatabase- let- compiler = T.display buildCompilerFlavor- CompilerId _ version' = buildCompilerId- ver = maybe (T.display version') T.display $ lookup compiler res- return $ compiler ++ "-" ++ ver---- | Get arch for stack-stackArch :: String-stackArch = T.display buildArch---- | Invoke stack command, we are trying to get actual stack near current hsdev executable-stack :: MonadLog m => [String] -> m String-stack cmd' = hsdevLiftIO $ do- curExe <- liftIO getExecutablePath- stackExe <- Util.withCurrentDirectory (takeDirectory curExe) $- liftIO (findExecutable "stack") >>= maybe (hsdevError $ ToolNotFound "stack") return- comp <- stackCompiler- liftIO $ runTool_ stackExe (["--compiler", comp, "--arch", stackArch] ++ cmd')---- | Make yaml opts-yaml :: Maybe FilePath -> [String]-yaml Nothing = []-yaml (Just y) = ["--stack-yaml", y]--type Paths = Map String FilePath---- | Stack path-path :: MonadLog m => Maybe FilePath -> m Paths-path mcfg = liftM (M.fromList . map breakPath . lines) $ stack ("path" : yaml mcfg) where- breakPath :: String -> (String, FilePath)- breakPath = second (dropWhile isSpace . drop 1) . break (== ':')---- | Get path for-pathOf :: String -> Lens' Paths (Maybe FilePath)-pathOf = at---- | Build stack project-build :: MonadLog m => [String] -> Maybe FilePath -> m ()-build opts mcfg = void $ stack $ "build" : (opts ++ yaml mcfg)---- | Build only dependencies-buildDeps :: MonadLog m => Maybe FilePath -> m ()-buildDeps = build ["--only-dependencies"]---- | Configure project-configure :: MonadLog m => Maybe FilePath -> m ()-configure = build ["--only-configure"]--data StackEnv = StackEnv {- _stackRoot :: FilePath,- _stackProject :: FilePath,- _stackConfig :: FilePath,- _stackGhc :: FilePath,- _stackSnapshot :: FilePath,- _stackLocal :: FilePath }--makeLenses ''StackEnv--getStackEnv :: Paths -> Maybe StackEnv-getStackEnv p = StackEnv <$>- (p ^. pathOf "stack-root") <*>- (p ^. pathOf "project-root") <*>- (p ^. pathOf "config-location") <*>- (p ^. pathOf "ghc-paths") <*>- (p ^. pathOf "snapshot-pkg-db") <*>- (p ^. pathOf "local-pkg-db")---- | Projects paths-projectEnv :: MonadLog m => FilePath -> m StackEnv-projectEnv p = hsdevLiftIO $ Util.withCurrentDirectory p $ do- paths' <- path Nothing- maybe (hsdevError $ ToolError "stack" ("can't get paths for " ++ p)) return $ getStackEnv paths'---- | Get package-db stack for stack environment-stackPackageDbStack :: Lens' StackEnv PackageDbStack-stackPackageDbStack = lens g s where- g :: StackEnv -> PackageDbStack- g env' = PackageDbStack $ map PackageDb [_stackLocal env', _stackSnapshot env']- s :: StackEnv -> PackageDbStack -> StackEnv- s env' pdbs = env' {- _stackSnapshot = fromMaybe (_stackSnapshot env') $ pdbs ^? packageDbStack . ix 1 . packageDb,- _stackLocal = fromMaybe (_stackLocal env') $ pdbs ^? packageDbStack . ix 0 . packageDb }+{-# LANGUAGE TemplateHaskell, RankNTypes #-} + +module HsDev.Stack ( + stack, yaml, + path, pathOf, + build, buildDeps, configure, + StackEnv(..), stackRoot, stackProject, stackConfig, stackGhc, stackSnapshot, stackLocal, + getStackEnv, projectEnv, + stackPackageDbStack, + + stackCompiler, stackArch, + + MaybeT(..) + ) where + +import Control.Arrow +import Control.Lens (makeLenses, Lens', at, ix, lens, (^?), (^.)) +import Control.Monad +import Control.Monad.Trans.Maybe +import Control.Monad.IO.Class +import Data.Char +import Data.Maybe +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Distribution.Compiler +import Distribution.System +import qualified Distribution.Text as T (display) +import System.Directory +import System.Environment +import System.FilePath + +import qualified GHC +import qualified Packages as GHC + +import HsDev.Error +import HsDev.PackageDb +import HsDev.Tools.Ghc.Worker (GhcM, tmpSession) +import qualified HsDev.Tools.Ghc.Compat as Compat +import HsDev.Util as Util +import HsDev.Tools.Base (runTool_) +import qualified System.Directory.Paths as P + +-- | Get compiler version +stackCompiler :: GhcM String +stackCompiler = do + tmpSession ["-no-user-package-db"] + df <- GHC.getSessionDynFlags + let + res = + map (GHC.packageNameString &&& GHC.packageVersion) . + fromMaybe [] . + Compat.pkgDatabase $ df + compiler = T.display buildCompilerFlavor + CompilerId _ version' = buildCompilerId + ver = maybe (T.display version') T.display $ lookup compiler res + return $ compiler ++ "-" ++ ver + +-- | Get arch for stack +stackArch :: String +stackArch = T.display buildArch + +-- | Invoke stack command, we are trying to get actual stack near current hsdev executable +stack :: [String] -> GhcM String +stack cmd' = hsdevLiftIO $ do + curExe <- liftIO getExecutablePath + stackExe <- Util.withCurrentDirectory (takeDirectory curExe) $ + liftIO (findExecutable "stack") >>= maybe (hsdevError $ ToolNotFound "stack") return + comp <- stackCompiler + liftIO $ runTool_ stackExe (["--compiler", comp, "--arch", stackArch] ++ cmd') + +-- | Make yaml opts +yaml :: Maybe FilePath -> [String] +yaml Nothing = [] +yaml (Just y) = ["--stack-yaml", y] + +type PathsConf = Map String FilePath + +-- | Stack path +path :: Maybe FilePath -> GhcM PathsConf +path mcfg = liftM (M.fromList . map breakPath . lines) $ stack ("path" : yaml mcfg) where + breakPath :: String -> (String, FilePath) + breakPath = second (dropWhile isSpace . drop 1) . break (== ':') + +-- | Get path for +pathOf :: String -> Lens' PathsConf (Maybe FilePath) +pathOf = at + +-- | Build stack project +build :: [String] -> Maybe FilePath -> GhcM () +build opts mcfg = void $ stack $ "build" : (opts ++ yaml mcfg) + +-- | Build only dependencies +buildDeps :: Maybe FilePath -> GhcM () +buildDeps = build ["--only-dependencies"] + +-- | Configure project +configure :: Maybe FilePath -> GhcM () +configure = build ["--only-configure"] + +data StackEnv = StackEnv { + _stackRoot :: FilePath, + _stackProject :: FilePath, + _stackConfig :: FilePath, + _stackGhc :: FilePath, + _stackSnapshot :: FilePath, + _stackLocal :: FilePath } + +makeLenses ''StackEnv + +getStackEnv :: PathsConf -> Maybe StackEnv +getStackEnv p = StackEnv <$> + (p ^. pathOf "stack-root") <*> + (p ^. pathOf "project-root") <*> + (p ^. pathOf "config-location") <*> + (p ^. pathOf "ghc-paths") <*> + (p ^. pathOf "snapshot-pkg-db") <*> + (p ^. pathOf "local-pkg-db") + +-- | Projects paths +projectEnv :: FilePath -> GhcM StackEnv +projectEnv p = hsdevLiftIO $ Util.withCurrentDirectory p $ do + paths' <- path Nothing + maybe (hsdevError $ ToolError "stack" ("can't get paths for " ++ p)) return $ getStackEnv paths' + +-- | Get package-db stack for stack environment +stackPackageDbStack :: Lens' StackEnv PackageDbStack +stackPackageDbStack = lens g s where + g :: StackEnv -> PackageDbStack + g env' = PackageDbStack $ map (PackageDb . P.fromFilePath) [_stackLocal env', _stackSnapshot env'] + s :: StackEnv -> PackageDbStack -> StackEnv + s env' pdbs = env' { + _stackSnapshot = fromMaybe (_stackSnapshot env') $ pdbs ^? packageDbStack . ix 1 . packageDb . P.path, + _stackLocal = fromMaybe (_stackLocal env') $ pdbs ^? packageDbStack . ix 0 . packageDb . P.path }
src/HsDev/Symbols.hs view
@@ -1,278 +1,127 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Symbols (- -- * Information- export,- passThingSpec, passImportSpec, imported, exported,- importNames, import_,- Symbol(..),- unnamedModuleId,- sortDeclarations, moduleLocals,- setDefinedIn, dropExternals, clearDefinedIn,- moduleLocalDeclarations, moduleModuleDeclarations,- Locals(..),- decl, definedIn, declarationLocals, scopes,- mergeExported,-- -- * Functions- importQualifier,-- -- * Utility- locateProject, searchProject,- locateSourceDir,- standaloneInfo,- moduleOpts,-- -- * Modifiers- addDeclaration,-- -- * Other- unalias,-- -- * Tags- setTag, hasTag, removeTag, dropTags,-- -- * Reexportss- module HsDev.Symbols.Types,- module HsDev.Symbols.Class,- module HsDev.Symbols.Documented- ) where--import Control.Applicative-import Control.Arrow-import Control.Lens-import Control.Monad.Trans.Maybe-import Control.Monad.Except-import Data.Function (on)-import Data.List-import Data.Maybe (fromMaybe, listToMaybe, catMaybes)-import qualified Data.Map as M-import Data.Ord (comparing)-import qualified Data.Set as S-import Data.Text (Text)-import qualified Data.Text as T (concat)-import System.Directory-import System.FilePath--import HsDev.Symbols.Types-import HsDev.Symbols.Class-import HsDev.Symbols.Documented (Documented(..))-import HsDev.Util (searchPath, uniqueBy, directoryContents)---- | Get name of export-export :: Export -> Text-export (ExportName Nothing n _) = n-export (ExportName (Just q) n _) = T.concat [q, ".", n]-export (ExportModule m) = m---- | Does name pass thing spec-passThingSpec :: Text -> ThingPart -> Bool-passThingSpec _ ThingNothing = False-passThingSpec _ ThingAll = True-passThingSpec n (ThingWith ns) = n `elem` ns---- | Does declaration pass import-list spec-passImportSpec :: Declaration -> ImportSpec -> Bool-passImportSpec decl' (ImportSpec n p)- | view declarationName decl' == n = True- | preview (declaration . related . _Just) decl' == Just n = view declarationName decl' `passThingSpec` p- | otherwise = False---- | Check whether declaration passes import list-imported :: Declaration -> ImportList -> Bool-imported decl' (ImportList hiding specs) = invert $ any (decl' `passImportSpec`) specs where- invert = if hiding then not else id---- | Check whether declaration passes export-exported :: Declaration -> Export -> Bool-exported decl' (ExportName q n p)- | view declarationName decl' == n = checkImport- | preview (declaration . related . _Just) decl' == Just n = view declarationName decl' `passThingSpec` p- | otherwise = False- where- checkImport = case q of- Nothing -> any (not . view importIsQualified) $ fromMaybe [] $ view declarationImported decl'- Just q' -> any ((q' `elem`) . importNames) $ fromMaybe [] $ view declarationImported decl'-exported decl' (ExportModule m) = any (unqualBy m) . fromMaybe [] . view declarationImported $ decl' where- unqualBy :: Text -> Import -> Bool- unqualBy m' i = m' `elem` importNames i && not (view importIsQualified i)---- | Get import module names - full and synonym-importNames :: Import -> [Text]-importNames i = view importModuleName i : maybe [] return (view importAs i)---- | Simple import-import_ :: Text -> Import-import_ n = Import n False Nothing Nothing Nothing---- | Imported module can be accessed via qualifier-importQualifier :: Maybe Text -> Import -> Bool-importQualifier Nothing i- | not (view importIsQualified i) = True- | otherwise = False-importQualifier (Just q) i- | q == view importModuleName i = True- | Just q == view importAs i = True- | otherwise = False--unnamedModuleId :: ModuleLocation -> ModuleId-unnamedModuleId = ModuleId ""--sortDeclarations :: [Declaration] -> [Declaration]-sortDeclarations = sortBy (comparing (view declarationName))---- | Bring locals to top-moduleLocals :: Module -> Module-moduleLocals m = set moduleDeclarations (moduleLocalDeclarations m) m---- | Set all declaration `definedIn` to this module-setDefinedIn :: Module -> Module-setDefinedIn m = over moduleDeclarations (map (`definedIn` view moduleId m)) m---- | Drop all declarations, that not defined in this module-dropExternals :: Module -> Module-dropExternals m = over moduleDeclarations (filter ((/= Just (view moduleId m)) . view declarationDefined)) m---- | Clear `definedIn` information-clearDefinedIn :: Module -> Module-clearDefinedIn = over moduleDeclarations (map (set declarationDefined Nothing))---- | Get declarations with locals-moduleLocalDeclarations :: Module -> [Declaration]-moduleLocalDeclarations =- sortDeclarations .- concatMap declarationLocals' .- view moduleDeclarations- where- declarationLocals' :: Declaration -> [Declaration]- declarationLocals' d = d : declarationLocals d---- | Get list of declarations as ModuleDeclaration-moduleModuleDeclarations :: Module -> [ModuleDeclaration]-moduleModuleDeclarations m = [ModuleDeclaration (view moduleId m) d | d <- view moduleDeclarations m]--class Locals a where- locals :: a -> [Declaration]- where_ :: a -> [Declaration] -> a--instance Locals Declaration where- locals = locals . view declaration- where_ d ds = over declaration (`where_` ds) d--decl :: Text -> DeclarationInfo -> Declaration-decl n = Declaration n Nothing Nothing Nothing Nothing--definedIn :: Declaration -> ModuleId -> Declaration-definedIn d m = set declarationDefined (Just m) d--declarationLocals :: Declaration -> [Declaration]-declarationLocals d = locals $ view declaration d---- | Get scopes of @Declaration@, where @Nothing@ is global scope-scopes :: Declaration -> [Maybe Text]-scopes d = globalScope $ concatMap (map Just . importNames) is where- is = fromMaybe [] $ view declarationImported d- globalScope- | any (not . view importIsQualified) is = (Nothing :)- | otherwise = id--instance Locals DeclarationInfo where- locals (Function _ ds _) = ds- locals _ = []- where_ (Function n s r) ds = Function n (s ++ ds) r- where_ d _ = d---- | Merge @ModuleDeclaration@ into @ExportedDeclaration@-mergeExported :: [ModuleDeclaration] -> [ExportedDeclaration]-mergeExported =- map merge' .- groupBy ((==) `on` declId) .- sortBy (comparing declId)- where- declId :: ModuleDeclaration -> (Text, Maybe ModuleId)- declId = view moduleDeclaration >>> (view declarationName &&& view declarationDefined)- merge' :: [ModuleDeclaration] -> ExportedDeclaration- merge' [] = error "mergeExported: impossible"- merge' ds@(d:_) = ExportedDeclaration (map (view declarationModuleId) ds) (view moduleDeclaration d)---- | Find project file is related to-locateProject :: FilePath -> IO (Maybe Project)-locateProject file = do- file' <- canonicalizePath file- isDir <- doesDirectoryExist file'- if isDir then locateHere file' else locateParent (takeDirectory file')- where- locateHere path = do- cts <- filter (not . null . takeBaseName) <$> directoryContents path- return $ fmap (project . (path </>)) $ find ((== ".cabal") . takeExtension) cts- locateParent dir = do- cts <- filter (not . null . takeBaseName) <$> directoryContents dir- case find ((== ".cabal") . takeExtension) cts of- Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir)- Just cabalf -> return $ Just $ project (dir </> cabalf)---- | Search project up-searchProject :: FilePath -> IO (Maybe Project)-searchProject file = runMaybeT $ searchPath file (MaybeT . locateProject) <|> mzero---- | Locate source dir of file-locateSourceDir :: FilePath -> IO (Maybe (Extensions FilePath))-locateSourceDir f = runMaybeT $ do- file <- liftIO $ canonicalizePath f- p <- MaybeT $ locateProject file- proj <- lift $ loadProject p- MaybeT $ return $ findSourceDir proj file---- | Make `Info` for standalone `Module`-standaloneInfo :: [PackageConfig] -> Module -> Info-standaloneInfo pkgs m = mempty { _infoDepends = pkgDeps ^.. each . package . packageName } where- pkgDeps = catMaybes [M.lookup mdep pkgMap >>= listToMaybe | mdep <- "Prelude" : (m ^.. moduleImports . each . importModuleName)]- pkgMap = M.unionsWith mergePkgs [M.singleton m' [p] | p <- pkgs, m' <- view packageModules p]- mergePkgs ls rs = if null es then hs else es where- (es, hs) = partition (view packageExposed) $ uniqueBy (view package) (ls ++ rs)---- | Options for GHC of module and project-moduleOpts :: [PackageConfig] -> Module -> [String]-moduleOpts pkgs m = case view moduleLocation m of- FileModule file proj -> concat [- hidePackages,- targetOpts info']- where- infos' = maybe [standaloneInfo pkgs m] (`fileTargets` file) proj- info' = over infoDepends (filter validDep) (mconcat $ selfInfo : infos')- selfInfo- | proj ^? _Just . projectName `elem` map Just (infos' ^.. each . infoDepends . each) = fromMaybe mempty $- proj ^? _Just . projectDescription . _Just . projectLibrary . _Just . libraryBuildInfo- | otherwise = mempty- -- filter out unavailable packages such as unix under windows- validDep d = d `elem` pkgs'- pkgs' = pkgs ^.. each . package . packageName- hidePackages- | null (info' ^. infoDepends) = []- | otherwise = ["-hide-all-packages"]- _ -> []---- | Add declaration to module-addDeclaration :: Declaration -> Module -> Module-addDeclaration decl' = over moduleDeclarations (sortDeclarations . (decl' :))---- | Unalias import name-unalias :: Module -> Text -> [Text]-unalias m alias = [view importModuleName i | i <- view moduleImports m, view importAs i == Just alias]---- | Set tag to `Inspected`-setTag :: Ord t => t -> Inspected i t a -> Inspected i t a-setTag tag = over inspectionTags (S.insert tag)---- | Check whether `Inspected` has tag-hasTag :: Ord t => t -> Inspected i t a -> Bool-hasTag tag = has (inspectionTags . ix tag)---- | Drop tag from `Inspected`-removeTag :: Ord t => t -> Inspected i t a -> Inspected i t a-removeTag tag = over inspectionTags (S.delete tag)---- | Drop all tags-dropTags :: Inspected i t a -> Inspected i t a-dropTags = set inspectionTags S.empty+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Symbols ( + -- * Utility + locateProject, searchProject, + locateSourceDir, + standaloneInfo, + moduleOpts, projectTargetOpts, + + -- * Tags + setTag, hasTag, removeTag, dropTags, + + -- * Reexportss + module HsDev.Symbols.Types, + module HsDev.Symbols.Class, + module HsDev.Symbols.Documented, + module HsDev.Symbols.HaskellNames + ) where + +import Control.Applicative +import Control.Lens +import Control.Monad.Trans.Maybe +import Control.Monad.Except +import Data.List +import Data.Maybe (fromMaybe, listToMaybe, catMaybes) +import qualified Data.Map.Strict as M +import qualified Data.Set as S +import System.Directory +import System.FilePath + +import HsDev.Symbols.Types +import HsDev.Symbols.Class +import HsDev.Symbols.Documented (Documented(..)) +import HsDev.Symbols.HaskellNames +import HsDev.Util (searchPath, uniqueBy, directoryContents) +import System.Directory.Paths + +-- | Find project file is related to +locateProject :: FilePath -> IO (Maybe Project) +locateProject file = do + file' <- canonicalizePath file + isDir <- doesDirectoryExist file' + if isDir then locateHere file' else locateParent (takeDirectory file') + where + locateHere p = do + cts <- filter (not . null . takeBaseName) <$> directoryContents p + return $ fmap (project . (p </>)) $ find ((== ".cabal") . takeExtension) cts + locateParent dir = do + cts <- filter (not . null . takeBaseName) <$> directoryContents dir + case find ((== ".cabal") . takeExtension) cts of + Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir) + Just cabalf -> return $ Just $ project (dir </> cabalf) + +-- | Search project up +searchProject :: FilePath -> IO (Maybe Project) +searchProject file = runMaybeT $ searchPath file (MaybeT . locateProject) <|> mzero + +-- | Locate source dir of file +locateSourceDir :: FilePath -> IO (Maybe (Extensions Path)) +locateSourceDir f = runMaybeT $ do + file <- liftIO $ canonicalizePath f + p <- MaybeT $ locateProject file + proj <- lift $ loadProject p + MaybeT $ return $ findSourceDir proj (fromFilePath file) + +-- | Make `Info` for standalone `Module` +standaloneInfo :: [PackageConfig] -> Module -> Info +standaloneInfo pkgs m = mempty { _infoDepends = pkgDeps ^.. each . package . packageName } where + pkgDeps = catMaybes [M.lookup mdep pkgMap >>= listToMaybe | mdep <- "Prelude" : imps] + pkgMap = M.unionsWith mergePkgs [M.singleton m' [p] | p <- pkgs, m' <- view packageModules p] + mergePkgs ls rs = if null es then hs else es where + (es, hs) = partition (view packageExposed) $ uniqueBy (view package) (ls ++ rs) + imps = delete (view (moduleId . moduleName) m) (m ^. moduleImports) + +-- | Options for GHC of module and project +moduleOpts :: [PackageConfig] -> Module -> [String] +moduleOpts pkgs m = case view (moduleId . moduleLocation) m of + FileModule file proj -> concat [ + hidePackages, + targetOpts absInfo] + where + infos' = maybe [standaloneInfo pkgs m] (`fileTargets` file) proj + info' = over infoDepends (filter validDep) (mconcat $ selfInfo : infos') + absInfo = maybe id (absolutise . view projectPath) proj info' + selfInfo + | proj ^? _Just . projectName `elem` map Just (infos' ^.. each . infoDepends . each) = fromMaybe mempty $ + proj ^? _Just . projectDescription . _Just . projectLibrary . _Just . libraryBuildInfo + | otherwise = mempty + -- filter out unavailable packages such as unix under windows + validDep d = d `elem` pkgs' + pkgs' = pkgs ^.. each . package . packageName + hidePackages + | null (info' ^. infoDepends) = [] + | otherwise = ["-hide-all-packages"] + _ -> [] + +-- | Options for GHC of project +projectTargetOpts :: [PackageConfig] -> Project -> Info -> [String] +projectTargetOpts pkgs proj info = concat [hidePackages, targetOpts absInfo] where + info' = over infoDepends (filter validDep) (selfInfo `mappend` info) + absInfo = absolutise (view projectPath proj) info' + selfInfo + | proj ^. projectName `elem` (info ^.. infoDepends . each) = fromMaybe mempty $ + proj ^? projectDescription . _Just . projectLibrary . _Just . libraryBuildInfo + | otherwise = mempty + validDep d = d `elem` pkgs' + pkgs' = pkgs ^.. each . package . packageName + hidePackages + | null (info' ^. infoDepends) = [] + | otherwise = ["-hide-all-packages"] + +-- | Set tag to `Inspected` +setTag :: Ord t => t -> Inspected i t a -> Inspected i t a +setTag tag' = over inspectionTags (S.insert tag') + +-- | Check whether `Inspected` has tag +hasTag :: Ord t => t -> Inspected i t a -> Bool +hasTag tag' = has (inspectionTags . ix tag') + +-- | Drop tag from `Inspected` +removeTag :: Ord t => t -> Inspected i t a -> Inspected i t a +removeTag tag' = over inspectionTags (S.delete tag') + +-- | Drop all tags +dropTags :: Inspected i t a -> Inspected i t a +dropTags = set inspectionTags S.empty
src/HsDev/Symbols/Class.hs view
@@ -1,20 +1,30 @@-module HsDev.Symbols.Class (- Symbol(..),- symbolModuleLocation,-- module HsDev.Symbols.Location- ) where--import Control.Lens (view)-import Data.Text (Text)--import HsDev.Symbols.Location--class Symbol a where- symbolName :: a -> Text- symbolQualifiedName :: a -> Text- symbolDocs :: a -> Maybe Text- symbolLocation :: a -> Location--symbolModuleLocation :: Symbol a => a -> ModuleLocation-symbolModuleLocation = view locationModule . symbolLocation+module HsDev.Symbols.Class ( + Sourced(..), + sourcedModuleName, + + module HsDev.Symbols.Location + ) where + +import Control.Lens (Lens', Traversal') +import Data.Text (Text) + +import HsDev.Symbols.Location + +class Sourced a where + sourcedName :: Lens' a Text + sourcedDocs :: Traversal' a Text + sourcedModule :: Lens' a ModuleId + sourcedLocation :: Traversal' a Position + sourcedDocs _ = pure + sourcedLocation _ = pure + +instance Sourced ModuleId where + sourcedName = moduleName + sourcedModule = id + +instance Sourced SymbolId where + sourcedName = symbolName + sourcedModule = symbolModule + +sourcedModuleName :: Sourced a => Lens' a Text +sourcedModuleName = sourcedModule . sourcedName
src/HsDev/Symbols/Documented.hs view
@@ -1,24 +1,62 @@-module HsDev.Symbols.Documented (- Documented(..),- defaultDetailed- ) where--import Data.Text (unpack)--import HsDev.Symbols.Class---- | Documented symbol-class Symbol a => Documented a where- brief :: a -> String- detailed :: a -> String- detailed = unlines . defaultDetailed---- | Default detailed docs-defaultDetailed :: Documented a => a -> [String]-defaultDetailed s = header ++ docs ++ loc where- header = [brief s, ""]- docs = maybe [] (return . unpack) $ symbolDocs s- loc- | null mloc = []- | otherwise = ["Defined at " ++ mloc]- mloc = show (symbolLocation s)+{-# LANGUAGE DefaultSignatures, OverloadedStrings #-} + +module HsDev.Symbols.Documented ( + Documented(..), + defaultDetailed + ) where + +import Control.Lens (view, (^..), (^?)) +import Data.Maybe (maybeToList) +import Data.Text (Text, pack) +import qualified Data.Text as T + +import Text.Format +import HsDev.Symbols.Class +import HsDev.Project.Types + +-- | Documented symbol +class Documented a where + brief :: a -> Text + detailed :: a -> Text + default detailed :: Sourced a => a -> Text + detailed = T.unlines . defaultDetailed + +-- | Default detailed docs +defaultDetailed :: (Sourced a, Documented a) => a -> [Text] +defaultDetailed s = concat [header, docs, loc] where + header = [brief s, ""] + docs = s ^.. sourcedDocs + loc = maybe [] (\l -> ["Defined at " `T.append` pack (show l)]) (s ^? sourcedLocation) + +instance Documented ModulePackage where + brief = pack . show + detailed = brief + +instance Documented ModuleLocation where + brief (FileModule f _) = f + brief (InstalledModule _ pkg n) = format "{} from {}" ~~ n ~~ brief pkg + brief (OtherLocation src) = src + brief NoLocation = "<no-location>" + detailed (FileModule f mproj) = case mproj of + Nothing -> f + Just proj -> format "{} in project {}" ~~ f ~~ brief proj + detailed (InstalledModule pdb pkg n) = format "{} from {} ({})" ~~ n ~~ brief pkg ~~ show pdb + detailed l = brief l + +instance Documented Project where + brief p = format "{} ({})" ~~ view projectName p ~~ view projectPath p + detailed p = T.unlines (brief p : desc) where + desc = concat [ + do + d <- mdescr + _ <- maybeToList $ view projectLibrary d + return "\tlibrary", + do + d <- mdescr + exe <- view projectExecutables d + return $ format "\texecutable: {}" ~~ view executableName exe, + do + d <- mdescr + test <- view projectTests d + return $ format "\ttest: {}" ~~ view testName test] + mdescr = maybeToList $ view projectDescription p
+ src/HsDev/Symbols/HaskellNames.hs view
@@ -0,0 +1,61 @@+{-# LANGUAGE FlexibleInstances #-} + +module HsDev.Symbols.HaskellNames ( + ToEnvironment(..), + fromSymbol, toSymbol + ) where + +import Control.Lens (view) +import Data.String +import qualified Data.Map.Strict as M +import qualified Data.Text as T (unpack) +import qualified Language.Haskell.Exts as H +import qualified Language.Haskell.Names as N + +import HsDev.Symbols.Types + +class ToEnvironment a where + environment :: a -> N.Environment + +instance ToEnvironment Module where + environment m = M.singleton (H.ModuleName () (T.unpack $ view sourcedName m)) (map toSymbol $ view moduleExports m) + +instance ToEnvironment [Module] where + environment = M.unions . map environment + +fromSymbol :: N.Symbol -> Symbol +fromSymbol s = Symbol sid Nothing Nothing info where + sid = SymbolId (fromName_ $ N.symbolName s) mid + mid = case N.symbolModule s of + H.ModuleName _ m -> ModuleId (fromString m) NoLocation + info = case s of + N.Value _ _ -> Function mempty + N.Method _ _ p -> Method mempty (fromName_ p) + N.Selector _ _ p cs -> Selector mempty (fromName_ p) (map fromName_ cs) + N.Constructor _ _ p -> Constructor mempty (fromName_ p) + N.Type _ _ -> Type mempty mempty + N.NewType _ _ -> NewType mempty mempty + N.Data _ _ -> Data mempty mempty + N.Class _ _ -> Class mempty mempty + N.TypeFam _ _ a -> TypeFam mempty mempty (fmap fromName_ a) + N.DataFam _ _ a -> DataFam mempty mempty (fmap fromName_ a) + N.PatternConstructor _ _ p -> PatConstructor mempty (fmap fromName_ p) + N.PatternSelector _ _ p c -> PatSelector mempty (fmap fromName_ p) (fromName_ c) + +toSymbol :: Symbol -> N.Symbol +toSymbol s = case view symbolInfo s of + Function _ -> N.Value m n + Method _ p -> N.Method m n (toName_ p) + Selector _ p cs -> N.Selector m n (toName_ p) (map toName_ cs) + Constructor _ p -> N.Constructor m n (toName_ p) + Type _ _ -> N.Type m n + NewType _ _ -> N.NewType m n + Data _ _ -> N.Data m n + Class _ _ -> N.Class m n + TypeFam _ _ a -> N.TypeFam m n (fmap toName_ a) + DataFam _ _ a -> N.DataFam m n (fmap toName_ a) + PatConstructor _ p -> N.PatternConstructor m n (fmap toName_ p) + PatSelector _ p c -> N.PatternSelector m n (fmap toName_ p) (toName_ c) + where + m = toModuleName_ $ view sourcedModuleName s + n = toName_ $ view sourcedName s
src/HsDev/Symbols/Location.hs view
@@ -1,277 +1,353 @@-{-# LANGUAGE OverloadedStrings, TemplateHaskell #-}--module HsDev.Symbols.Location (- ModulePackage(..), PackageConfig(..), ModuleLocation(..), moduleStandalone, locationId, noLocation,- Position(..), Region(..), region, regionAt, regionLines, regionStr,- Location(..),-- packageName, packageVersion,- package, packageModules, packageExposed,- moduleFile, moduleProject, modulePackageDb, modulePackage, cabalModuleName, moduleSourceName,- positionLine, positionColumn,- regionFrom, regionTo,- locationModule, locationPosition,-- sourceModuleRoot,- importedModulePath,- packageOpt,- RecalcTabs(..),-- module HsDev.PackageDb- ) where--import Control.Applicative-import Control.DeepSeq (NFData(..))-import Control.Lens (makeLenses, preview, view)-import Control.Monad (join)-import Data.Aeson-import Data.Char (isSpace, isDigit)-import Data.List (intercalate, findIndex)-import Data.Maybe-import Data.Text (Text)-import qualified Data.Text as T (split, unpack)-import System.FilePath-import Text.Read (readMaybe)--import System.Directory.Paths-import HsDev.PackageDb-import HsDev.Project.Types-import HsDev.Util ((.::), (.::?), (.::?!), objectUnion)---- | Just package name and version without its location-data ModulePackage = ModulePackage {- _packageName :: String,- _packageVersion :: String }- deriving (Eq, Ord)--makeLenses ''ModulePackage--instance NFData ModulePackage where- rnf (ModulePackage n v) = rnf n `seq` rnf v--instance Show ModulePackage where- show (ModulePackage n "") = n- show (ModulePackage n v) = n ++ "-" ++ v--instance Read ModulePackage where- readsPrec _ str = case pkg of- "" -> []- _ -> [(ModulePackage n v, str')]- where- (pkg, str') = break isSpace str- (rv, rn) = span versionChar $ reverse pkg- v = reverse rv- n = reverse $ dropWhile (== '-') rn-- versionChar ch = isDigit ch || ch == '.'--instance ToJSON ModulePackage where- toJSON (ModulePackage n v) = object [- "name" .= n,- "version" .= v]--instance FromJSON ModulePackage where- parseJSON = withObject "module package" $ \v ->- ModulePackage <$> (v .:: "name") <*> (v .:: "version")--data PackageConfig = PackageConfig {- _package :: ModulePackage,- _packageModules :: [Text],- _packageExposed :: Bool }- deriving (Eq, Ord, Read, Show)--makeLenses ''PackageConfig--instance NFData PackageConfig where- rnf (PackageConfig p ms e) = rnf p `seq` rnf ms `seq` rnf e--instance ToJSON PackageConfig where- toJSON (PackageConfig p ms e) = toJSON p `objectUnion` object ["modules" .= ms, "exposed" .= e]--instance FromJSON PackageConfig where- parseJSON = withObject "package-config" $ \v -> PackageConfig <$>- parseJSON (Object v) <*>- (v .::?! "modules") <*>- (v .:: "exposed" <|> pure False)---- | Location of module-data ModuleLocation =- FileModule { _moduleFile :: FilePath, _moduleProject :: Maybe Project } |- InstalledModule { _modulePackageDb :: PackageDb, _modulePackage :: Maybe ModulePackage, _cabalModuleName :: String } |- ModuleSource { _moduleSourceName :: Maybe String }- deriving (Eq, Ord)--makeLenses ''ModuleLocation--moduleStandalone :: ModuleLocation -> Bool-moduleStandalone = (== Just Nothing) . preview moduleProject--locationId :: ModuleLocation -> String-locationId (FileModule fpath _) = fpath-locationId (InstalledModule cabal mpack nm) = intercalate ":" [show cabal, maybe "" show mpack, nm]-locationId (ModuleSource msrc) = fromMaybe "" msrc--instance NFData ModuleLocation where- rnf (FileModule f p) = rnf f `seq` rnf p- rnf (InstalledModule d p n) = rnf d `seq` rnf p `seq` rnf n- rnf (ModuleSource m) = rnf m--instance Show ModuleLocation where- show (FileModule f p) = f ++ maybe "" (" in " ++) (fmap (view projectPath) p)- show (InstalledModule _ p n) = n ++ maybe "" (" in package " ++) (fmap show p)- show (ModuleSource m) = fromMaybe "" m--instance ToJSON ModuleLocation where- toJSON (FileModule f p) = object ["file" .= f, "project" .= fmap (view projectCabal) p]- toJSON (InstalledModule c p n) = object ["db" .= c, "package" .= fmap show p, "name" .= n]- toJSON (ModuleSource (Just s)) = object ["source" .= s]- toJSON (ModuleSource Nothing) = object []--instance FromJSON ModuleLocation where- parseJSON = withObject "module location" $ \v ->- (FileModule <$> v .:: "file" <*> (fmap project <$> (v .:: "project"))) <|>- (InstalledModule <$> v .:: "db" <*> fmap (join . fmap readMaybe) (v .:: "package") <*> v .:: "name") <|>- (ModuleSource <$> v .::? "source")--instance Paths ModuleLocation where- paths f (FileModule fpath p) = FileModule <$> f fpath <*> traverse (paths f) p- paths f (InstalledModule c p n) = InstalledModule <$> paths f c <*> pure p <*> pure n- paths _ (ModuleSource m) = pure $ ModuleSource m--noLocation :: ModuleLocation-noLocation = ModuleSource Nothing--data Position = Position {- _positionLine :: Int,- _positionColumn :: Int }- deriving (Eq, Ord, Read)--makeLenses ''Position--instance NFData Position where- rnf (Position l c) = rnf l `seq` rnf c--instance Show Position where- show (Position l c) = show l ++ ":" ++ show c--instance ToJSON Position where- toJSON (Position l c) = object [- "line" .= l,- "column" .= c]--instance FromJSON Position where- parseJSON = withObject "position" $ \v -> Position <$>- v .:: "line" <*>- v .:: "column"--data Region = Region {- _regionFrom :: Position,- _regionTo :: Position }- deriving (Eq, Ord, Read)--makeLenses ''Region--region :: Position -> Position -> Region-region f t = Region (min f t) (max f t)--regionAt :: Position -> Region-regionAt f = region f f--regionLines :: Region -> Int-regionLines (Region f t) = succ (view positionLine t - view positionLine f)---- | Get string at region-regionStr :: Region -> String -> String-regionStr r@(Region f t) s = intercalate "\n" $ drop (pred $ view positionColumn f) fline : tl where- s' = take (regionLines r) $ drop (pred (view positionLine f)) $ lines s- (fline:tl) = init s' ++ [take (pred $ view positionColumn t) (last s')]--instance NFData Region where- rnf (Region f t) = rnf f `seq` rnf t--instance Show Region where- show (Region f t) = show f ++ "-" ++ show t--instance ToJSON Region where- toJSON (Region f t) = object [- "from" .= f,- "to" .= t]--instance FromJSON Region where- parseJSON = withObject "region" $ \v -> Region <$>- v .:: "from" <*>- v .:: "to"---- | Location of symbol-data Location = Location {- _locationModule :: ModuleLocation,- _locationPosition :: Maybe Position }- deriving (Eq, Ord)--makeLenses ''Location--instance NFData Location where- rnf (Location m p) = rnf m `seq` rnf p--instance Show Location where- show (Location m p) = show m ++ ":" ++ show p--instance ToJSON Location where- toJSON (Location ml p) = object [- "module" .= ml,- "pos" .= p]--instance FromJSON Location where- parseJSON = withObject "location" $ \v -> Location <$>- v .:: "module" <*>- v .:: "pos"---- | Get source module root directory, i.e. for "...\src\Foo\Bar.hs" with module 'Foo.Bar' will return "...\src"-sourceModuleRoot :: Text -> FilePath -> FilePath-sourceModuleRoot mname = - joinPath .- reverse . drop (length $ T.split (== '.') mname) . reverse .- splitDirectories---- | Get path of imported module--- >importedModulePath "Foo.Bar" "...\src\Foo\Bar.hs" "Quux.Blah" = "...\src\Quux\Blah.hs"-importedModulePath :: Text -> FilePath -> Text -> FilePath-importedModulePath mname file imp =- (`addExtension` "hs") . joinPath .- (++ ipath) . splitDirectories $- sourceModuleRoot mname file- where- ipath = map T.unpack $ T.split (== '.') imp--packageOpt :: Maybe ModulePackage -> [String]-packageOpt = maybeToList . fmap (("-package " ++) . view packageName)---- | Recalc positions to interpret '\t' as one symbol instead of N-class RecalcTabs a where- -- | Interpret '\t' as one symbol instead of N- recalcTabs :: String -> Int -> a -> a- -- | Inverse of `recalcTabs`: interpret '\t' as N symbols instead of 1- calcTabs :: String -> Int -> a -> a--instance RecalcTabs Position where- recalcTabs cts n (Position l c) = Position l c' where- line = listToMaybe $ drop (pred l) $ lines cts- c' = case line of- Nothing -> c- Just line' -> let sizes = map charSize line' in- succ . fromMaybe (length sizes) .- findIndex (>= pred c) .- scanl (+) 0 $ sizes- charSize :: Char -> Int- charSize '\t' = n- charSize _ = 1- calcTabs cts n (Position l c) = Position l c' where- line = listToMaybe $ drop (pred l) $ lines cts- c' = maybe c (succ . sum . map charSize . take (pred c)) line- charSize :: Char -> Int- charSize '\t' = n- charSize _ = 1--instance RecalcTabs Region where- recalcTabs cts n (Region f t) = Region (recalcTabs cts n f) (recalcTabs cts n t)- calcTabs cts n (Region f t) = Region (calcTabs cts n f) (calcTabs cts n t)+{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} + +module HsDev.Symbols.Location ( + ModulePackage(..), mkPackage, PackageConfig(..), ModuleLocation(..), locationId, noLocation, + ModuleId(..), moduleName, moduleLocation, + SymbolId(..), symbolName, symbolModule, + Position(..), Region(..), region, regionAt, regionLines, regionStr, + Location(..), + + packageName, packageVersion, + package, packageModules, packageExposed, + moduleFile, moduleProject, moduleInstallDirs, modulePackage, installedModuleName, otherLocationName, + positionLine, positionColumn, + regionFrom, regionTo, + locationModule, locationPosition, + + sourceModuleRoot, + importPath, + sourceRoot, sourceRoot_, + RecalcTabs(..), + + module HsDev.PackageDb.Types + ) where + +import Control.Applicative +import Control.DeepSeq (NFData(..)) +import Control.Lens (makeLenses, view, preview, over) +import Data.Aeson +import Data.Char (isSpace, isDigit) +import Data.List (findIndex) +import Data.Maybe +import Data.Text (Text, pack, unpack) +import qualified Data.Text as T +import System.FilePath +import Text.Read (readMaybe) + +import System.Directory.Paths +import HsDev.PackageDb.Types +import HsDev.Project.Types +import HsDev.Util ((.::), (.::?), (.::?!), objectUnion, noNulls) + +-- | Just package name and version without its location +data ModulePackage = ModulePackage { + _packageName :: Text, + _packageVersion :: Text } + deriving (Eq, Ord) + +makeLenses ''ModulePackage + +mkPackage :: Text -> ModulePackage +mkPackage n = ModulePackage n "" + +instance NFData ModulePackage where + rnf (ModulePackage n v) = rnf n `seq` rnf v + +instance Show ModulePackage where + show (ModulePackage n "") = unpack n + show (ModulePackage n v) = unpack n ++ "-" ++ unpack v + +instance Read ModulePackage where + readsPrec _ str = case pkg of + "" -> [] + _ -> [(ModulePackage (pack n) (pack v), str')] + where + (pkg, str') = break isSpace str + (rv, rn) = span versionChar $ reverse pkg + v = reverse rv + n = reverse $ dropWhile (== '-') rn + + versionChar ch = isDigit ch || ch == '.' + +instance ToJSON ModulePackage where + toJSON (ModulePackage n v) = object [ + "name" .= n, + "version" .= v] + +instance FromJSON ModulePackage where + parseJSON = withObject "module package" $ \v -> + ModulePackage <$> (v .:: "name") <*> (v .:: "version") + +data PackageConfig = PackageConfig { + _package :: ModulePackage, + _packageModules :: [Text], + _packageExposed :: Bool } + deriving (Eq, Ord, Read, Show) + +makeLenses ''PackageConfig + +instance NFData PackageConfig where + rnf (PackageConfig p ms e) = rnf p `seq` rnf ms `seq` rnf e + +instance ToJSON PackageConfig where + toJSON (PackageConfig p ms e) = toJSON p `objectUnion` object ["modules" .= ms, "exposed" .= e] + +instance FromJSON PackageConfig where + parseJSON = withObject "package-config" $ \v -> PackageConfig <$> + parseJSON (Object v) <*> + (v .::?! "modules") <*> + (v .:: "exposed" <|> pure False) + +-- | Location of module +data ModuleLocation = + FileModule { _moduleFile :: Path, _moduleProject :: Maybe Project } | + InstalledModule { _moduleInstallDirs :: [Path], _modulePackage :: ModulePackage, _installedModuleName :: Text } | + OtherLocation { _otherLocationName :: Text } | + NoLocation + +instance Eq ModuleLocation where + FileModule lfile _ == FileModule rfile _ = lfile == rfile + InstalledModule ldirs _ lname == InstalledModule rdirs _ rname = ldirs == rdirs && lname == rname + OtherLocation l == OtherLocation r = l == r + NoLocation == NoLocation = True + _ == _ = False + +instance Ord ModuleLocation where + compare l r = compare (locType l, locNames l) (locType r, locNames r) where + locType :: ModuleLocation -> Int + locType (FileModule _ _) = 0 + locType (InstalledModule _ _ _) = 1 + locType (OtherLocation _) = 2 + locType NoLocation = 3 + locNames (FileModule f _) = [f] + locNames (InstalledModule dirs _ nm) = nm : dirs + locNames (OtherLocation n) = [n] + locNames NoLocation = [] + +makeLenses ''ModuleLocation + +locationId :: ModuleLocation -> Text +locationId (FileModule fpath _) = fpath +locationId (InstalledModule dirs mpack nm) = T.intercalate ":" (take 1 dirs ++ [pack (show mpack), nm]) +locationId (OtherLocation src) = src +locationId NoLocation = "<no-location>" + +instance NFData ModuleLocation where + rnf (FileModule f p) = rnf f `seq` rnf p + rnf (InstalledModule d p n) = rnf d `seq` rnf p `seq` rnf n + rnf (OtherLocation s) = rnf s + rnf NoLocation = () + +instance Show ModuleLocation where + show = unpack . locationId + +instance ToJSON ModuleLocation where + toJSON (FileModule f p) = object $ noNulls ["file" .= f, "project" .= fmap (view projectCabal) p] + toJSON (InstalledModule c p n) = object $ noNulls ["dirs" .= c, "package" .= show p, "name" .= n] + toJSON (OtherLocation s) = object ["source" .= s] + toJSON NoLocation = object [] + +instance FromJSON ModuleLocation where + parseJSON = withObject "module location" $ \v -> + (FileModule <$> v .:: "file" <*> (fmap project <$> (v .::? "project"))) <|> + (InstalledModule <$> v .::?! "dirs" <*> (readPackage =<< (v .:: "package")) <*> v .:: "name") <|> + (OtherLocation <$> v .:: "source") <|> + (pure NoLocation) + where + readPackage s = maybe (fail $ "can't parse package: " ++ s) return . readMaybe $ s + +instance Paths ModuleLocation where + paths f (FileModule fpath p) = FileModule <$> paths f fpath <*> traverse (paths f) p + paths f (InstalledModule c p n) = InstalledModule <$> traverse (paths f) c <*> pure p <*> pure n + paths _ (OtherLocation s) = pure $ OtherLocation s + paths _ NoLocation = pure NoLocation + +noLocation :: ModuleLocation +noLocation = NoLocation + +data ModuleId = ModuleId { + _moduleName :: Text, + _moduleLocation :: ModuleLocation } + deriving (Eq, Ord) + +makeLenses ''ModuleId + +instance NFData ModuleId where + rnf (ModuleId n l) = rnf n `seq` rnf l + +instance Show ModuleId where + show (ModuleId n l) = show l ++ ":" ++ unpack n + +instance ToJSON ModuleId where + toJSON m = object $ noNulls [ + "name" .= _moduleName m, + "location" .= _moduleLocation m] + +instance FromJSON ModuleId where + parseJSON = withObject "module-id" $ \v -> ModuleId <$> + (fromMaybe "" <$> (v .::? "name")) <*> + (fromMaybe NoLocation <$> (v .::? "location")) + +-- | Symbol +data SymbolId = SymbolId { + _symbolName :: Text, + _symbolModule :: ModuleId } + deriving (Eq, Ord) + +makeLenses ''SymbolId + +instance NFData SymbolId where + rnf (SymbolId n m) = rnf n `seq` rnf m + +instance Show SymbolId where + show (SymbolId n m) = show m ++ ":" ++ unpack n + +instance ToJSON SymbolId where + toJSON s = object $ noNulls [ + "name" .= _symbolName s, + "module" .= _symbolModule s] + +instance FromJSON SymbolId where + parseJSON = withObject "symbol-id" $ \v -> SymbolId <$> + (fromMaybe "" <$> (v .::? "name")) <*> + (fromMaybe (ModuleId "" NoLocation) <$> (v .::? "module")) + +data Position = Position { + _positionLine :: Int, + _positionColumn :: Int } + deriving (Eq, Ord, Read) + +makeLenses ''Position + +instance NFData Position where + rnf (Position l c) = rnf l `seq` rnf c + +instance Show Position where + show (Position l c) = show l ++ ":" ++ show c + +instance ToJSON Position where + toJSON (Position l c) = object [ + "line" .= l, + "column" .= c] + +instance FromJSON Position where + parseJSON = withObject "position" $ \v -> Position <$> + v .:: "line" <*> + v .:: "column" + +data Region = Region { + _regionFrom :: Position, + _regionTo :: Position } + deriving (Eq, Ord, Read) + +makeLenses ''Region + +region :: Position -> Position -> Region +region f t = Region (min f t) (max f t) + +regionAt :: Position -> Region +regionAt f = region f f + +regionLines :: Region -> Int +regionLines (Region f t) = succ (view positionLine t - view positionLine f) + +-- | Get string at region +regionStr :: Region -> Text -> Text +regionStr r@(Region f t) s = T.intercalate "\n" $ T.drop (pred $ view positionColumn f) fline : tl where + s' = take (regionLines r) $ drop (pred (view positionLine f)) $ T.lines s + (fline:tl) = init s' ++ [T.take (pred $ view positionColumn t) (last s')] + +instance NFData Region where + rnf (Region f t) = rnf f `seq` rnf t + +instance Show Region where + show (Region f t) = show f ++ "-" ++ show t + +instance ToJSON Region where + toJSON (Region f t) = object [ + "from" .= f, + "to" .= t] + +instance FromJSON Region where + parseJSON = withObject "region" $ \v -> Region <$> + v .:: "from" <*> + v .:: "to" + +-- | Location of symbol +data Location = Location { + _locationModule :: ModuleLocation, + _locationPosition :: Maybe Position } + deriving (Eq, Ord) + +makeLenses ''Location + +instance NFData Location where + rnf (Location m p) = rnf m `seq` rnf p + +instance Show Location where + show (Location m p) = show m ++ ":" ++ show p + +instance ToJSON Location where + toJSON (Location ml p) = object [ + "module" .= ml, + "pos" .= p] + +instance FromJSON Location where + parseJSON = withObject "location" $ \v -> Location <$> + v .:: "module" <*> + v .::? "pos" + +-- | Get source module root directory, i.e. for "...\src\Foo\Bar.hs" with module 'Foo.Bar' will return "...\src" +sourceModuleRoot :: Text -> Path -> Path +sourceModuleRoot mname = over paths $ + normalise . joinPath . + reverse . drop (length $ T.split (== '.') mname) . reverse . + splitDirectories + +-- | Path to module source +-- >importPath "Quux.Blah" = "Quux/Blah.hs" +importPath :: Text -> Path +importPath = fromFilePath . (`addExtension` "hs") . joinPath . map unpack . T.split (== '.') + +-- | Root of sources, package dir or root directory of standalone modules +sourceRoot :: ModuleId -> Maybe Path +sourceRoot m = do + fpath <- preview (moduleLocation . moduleFile) m + mproj <- preview (moduleLocation . moduleProject) m + return $ maybe + (sourceModuleRoot (view moduleName m) fpath) + (view projectPath) + mproj + +sourceRoot_ :: ModuleId -> Path +sourceRoot_ = fromMaybe (error "sourceRoot_: not a source location") . sourceRoot + +-- | Recalc positions to interpret '\t' as one symbol instead of N +class RecalcTabs a where + -- | Interpret '\t' as one symbol instead of N + recalcTabs :: Text -> Int -> a -> a + -- | Inverse of `recalcTabs`: interpret '\t' as N symbols instead of 1 + calcTabs :: Text -> Int -> a -> a + +instance RecalcTabs Position where + recalcTabs cts n (Position l c) = Position l c' where + line = listToMaybe $ drop (pred l) $ T.lines cts + c' = case line of + Nothing -> c + Just line' -> let sizes = map charSize (unpack line') in + succ . fromMaybe (length sizes) . + findIndex (>= pred c) . + scanl (+) 0 $ sizes + charSize :: Char -> Int + charSize '\t' = n + charSize _ = 1 + calcTabs cts n (Position l c) = Position l c' where + line = listToMaybe $ drop (pred l) $ T.lines cts + c' = maybe c (succ . sum . map charSize . take (pred c) . unpack) line + charSize :: Char -> Int + charSize '\t' = n + charSize _ = 1 + +instance RecalcTabs Region where + recalcTabs cts n (Region f t) = Region (recalcTabs cts n f) (recalcTabs cts n t) + calcTabs cts n (Region f t) = Region (calcTabs cts n f) (calcTabs cts n t)
+ src/HsDev/Symbols/Name.hs view
@@ -0,0 +1,82 @@+{-# LANGUAGE CPP, PatternSynonyms, ViewPatterns, OverloadedStrings #-} + +module HsDev.Symbols.Name ( + Name, qualName, unqualName, nameModule, nameIdent, pattern Name, fromName_, toName_, toModuleName_, fromModuleName_, fromName, toName, + ) where + +import Control.Arrow +import Data.Char (isAlpha, isAlphaNum) +import Data.Text (Text) +import Data.String (fromString) +import qualified Data.Text as T +import Language.Haskell.Exts (QName(..), ModuleName(..), Boxed(..), SpecialCon(..)) +import qualified Language.Haskell.Exts as Exts (Name(..)) + +-- | Qualified name +type Name = QName () + +qualName :: String -> String -> Name +qualName m = Qual () (ModuleName () m) . toName_ . fromString + +unqualName :: String -> Name +unqualName = UnQual () . toName_ . fromString + +nameModule :: Name -> Maybe Text +nameModule (Qual _ (ModuleName _ m) _) = Just $ fromString m +nameModule _ = Nothing + +nameIdent :: Name -> Text +nameIdent (Qual _ _ n) = fromName_ n +nameIdent (UnQual _ n) = fromName_ n +nameIdent s = fromName s + +pattern Name :: Maybe Text -> Text -> Name +pattern Name m n <- ((nameModule &&& nameIdent) -> (m, n)) where + Name Nothing n = UnQual () (Exts.Ident () (T.unpack n)) + Name (Just m) n = Qual () (ModuleName () (T.unpack m)) (Exts.Ident () (T.unpack n)) + +fromName_ :: Exts.Name () -> Text +fromName_ (Exts.Ident _ s') = fromString s' +fromName_ (Exts.Symbol _ s') = fromString s' + +toName_ :: Text -> Exts.Name () +toName_ txt + | T.null txt = Exts.Ident () "" + | isAlpha (T.head txt) && (T.all validChar $ T.tail txt) = Exts.Ident () . T.unpack $ txt + | otherwise = Exts.Symbol () . T.unpack $ txt + where + validChar ch = isAlphaNum ch || ch == '_' + +toModuleName_ :: Text -> ModuleName () +toModuleName_ = ModuleName () . T.unpack + +fromModuleName_ :: ModuleName () -> Text +fromModuleName_ (ModuleName () m) = T.pack m + +toName :: Text -> Name +toName "()" = Special () (UnitCon ()) +toName "[]" = Special () (ListCon ()) +toName "->" = Special () (FunCon ()) +toName "(:)" = Special () (Cons ()) +toName "(# #)" = Special () (UnboxedSingleCon ()) +toName tup + | T.all (== ',') noBraces = Special () (TupleCon () Boxed (succ $ T.length noBraces)) + where + noBraces = T.dropAround (`elem` ['(', ')']) tup +toName n = case T.split (== '.') n of + [n'] -> UnQual () (Exts.Ident () $ T.unpack n') + ns -> Qual () (ModuleName () (T.unpack $ T.intercalate "." $ init ns)) (toName_ $ last ns) + +fromName :: Name -> Text +fromName (Qual _ (ModuleName _ m) n) = T.concat [fromString m, ".", fromName_ n] +fromName (UnQual _ n) = fromName_ n +fromName (Special _ c) = case c of + UnitCon _ -> "()" + ListCon _ -> "[]" + FunCon _ -> "->" + TupleCon _ _ i -> fromString $ "(" ++ replicate (pred i) ',' ++ ")" + Cons _ -> "(:)" + UnboxedSingleCon _ -> "(# #)" +#if MIN_VERSION_haskell_src_exts(1,20,0) + ExprHole _ -> "_" +#endif
+ src/HsDev/Symbols/Parsed.hs view
@@ -0,0 +1,197 @@+{-# LANGUAGE RankNTypes #-} + +module HsDev.Symbols.Parsed ( + Ann, Parsed, + qnames, names, binders, locals, globals, references, unresolveds, + usages, named, imports, declarations, moduleNames, + + annL, symbolL, file, pos, defPos, resolvedName, + isBinder, isLocal, isGlobal, isReference, isUnresolved, resolveError, + refsTo, refsToName, + nameInfoL, positionL, regionL, fileL, + symbolNameL, + + prettyPrint + ) where + +import Control.Lens +import Data.Data +import Data.Data.Lens +import Data.Maybe (isJust) +import Data.Text (Text) +import Language.Haskell.Exts hiding (Name(..)) +import qualified Language.Haskell.Exts as E (Name(..)) +import Language.Haskell.Names + +import HsDev.Symbols.Name +import HsDev.Symbols.Location (Position(..), positionLine, positionColumn, Region(..), region) + +-- | Annotation of parsed and resolved nodes +type Ann = Scoped SrcSpanInfo + +-- | Parsed and resolved module +type Parsed = Module Ann + +-- | Get all qualified names +qnames :: Data (ast Ann) => Traversal' (ast Ann) (QName Ann) +qnames = biplate + +-- | Get all names +names :: Data (ast Ann) => Traversal' (ast Ann) (E.Name Ann) +names = biplate + +-- | Get all binders +binders :: Annotated ast => Traversal' (ast Ann) (ast Ann) +binders = filtered isBinder + +-- | Get all names locally defined +locals :: Annotated ast => Traversal' (ast Ann) (ast Ann) +locals = filtered isLocal + +-- | Get all names, references global symbol +globals :: Annotated ast => Traversal' (ast Ann) (ast Ann) +globals = filtered isGlobal + +-- | Get all resolved references +references :: Annotated ast => Traversal' (ast Ann) (ast Ann) +references = filtered isReference + +-- | Get all names with not in scope error +unresolveds :: Annotated ast => Traversal' (ast Ann) (ast Ann) +unresolveds = filtered isUnresolved + +-- | Get all usages of symbol +usages :: Annotated ast => Name -> Traversal' (ast Ann) (ast Ann) +usages = filtered . refsTo + +-- | Get usages of symbols with unqualified name +named :: Annotated ast => Text -> Traversal' (ast Ann) (ast Ann) +named = filtered . refsToName + +-- | Get imports +imports :: Data (ast Ann) => Traversal' (ast Ann) (ImportDecl Ann) +imports = biplate + +-- | Get declarations +declarations :: Data (ast Ann) => Traversal' (ast Ann) (Decl Ann) +declarations = biplate + +-- | Get module names +moduleNames :: Data (ast Ann) => Traversal' (ast Ann) (ModuleName Ann) +moduleNames = biplate + +-- | Get annotation +annL :: Annotated ast => Lens' (ast a) a +annL = lens ann (\v a' -> amap (const a') v) + +-- | Get haskell-names symbols +symbolL :: Data a => Traversal' a Symbol +symbolL = biplate + +-- | Get source file +file :: Annotated ast => Lens' (ast Ann) FilePath +file = annL . fileL + +-- | Get source location +pos :: Annotated ast => Lens' (ast Ann) Position +pos = annL . positionL + +-- | Definition position, if binder - returns current position +defPos :: Annotated ast => Traversal' (ast Ann) Position +defPos = annL . defLoc' where + defLoc' :: Traversal' Ann Position + defLoc' f (Scoped (LocalValue s) i) = Scoped <$> (LocalValue <$> positionL f s) <*> pure i + defLoc' f (Scoped (TypeVar s) i) = Scoped <$> (TypeVar <$> positionL f s) <*> pure i + defLoc' f (Scoped ValueBinder i) = Scoped ValueBinder <$> positionL f i + defLoc' f (Scoped TypeBinder i) = Scoped TypeBinder <$> positionL f i + defLoc' _ s = pure s + +-- | Resolved global name +resolvedName :: Annotated ast => Traversal' (ast Ann) Name +resolvedName = annL . nameInfoL . symbolL . symbolNameL + +-- | Does ast node binds something +isBinder :: Annotated ast => ast Ann -> Bool +isBinder e = (e ^. annL . nameInfoL) `elem` [TypeBinder, ValueBinder] + +-- | Does ast node locally defined +isLocal :: Annotated ast => ast Ann -> Bool +isLocal e = case e ^. annL . nameInfoL of + LocalValue _ -> True + TypeVar _ -> True + _ -> False + +-- | Does ast node reference something +isGlobal :: Annotated ast => ast Ann -> Bool +isGlobal e = case e ^. annL . nameInfoL of + GlobalSymbol _ _ -> True + _ -> False + +-- | Does ast node reference something +isReference :: Annotated ast => ast Ann -> Bool +isReference e = isLocal e || isGlobal e + +-- | Is ast node not resolved +isUnresolved :: Annotated ast => ast Ann -> Bool +isUnresolved = isJust . resolveError + +-- | Resolve error +resolveError :: Annotated ast => ast Ann -> Maybe String +resolveError e = case e ^. annL . nameInfoL of + ScopeError err -> Just $ ppError err + _ -> Nothing + +-- | Node references to specified symbol +refsTo :: Annotated ast => Name -> ast Ann -> Bool +refsTo n a = Just n == a ^? resolvedName + +-- | Node references to specified unqualified name +refsToName :: Annotated ast => Text -> ast Ann -> Bool +refsToName n a = Just n == fmap nameIdent (a ^? resolvedName) + +nameInfoL :: Lens' (Scoped a) (NameInfo a) +nameInfoL = lens g' s' where + g' (Scoped i _) = i + s' (Scoped _ s) i' = Scoped i' s + +positionL :: (SrcInfo isrc, Data isrc) => Lens' isrc Position +positionL = lens g' s' where + g' i = Position l c where + SrcLoc _ l c = getPointLoc i + s' i (Position l c) = over biplate upd i where + Position sl sc = g' i -- Old location + -- main line: set new line and move column + -- other lines: just move line, because altering first line's column doesn't affect other lines + upd :: SrcLoc -> SrcLoc + upd (SrcLoc f' l' c') + | l' == sl = SrcLoc f' l (c' - sc + c) + | otherwise = SrcLoc f' (l' - sl + l) c' + +regionL :: Annotated ast => Lens' (ast Ann) Region +regionL = lens g' s' where + g' i = case ann i of + Scoped _ sinfo -> toPos (srcSpanStart span') `region` toPos (srcSpanEnd span') where + span' = srcInfoSpan sinfo + toPos = uncurry Position + s' i (Region s e) = amap (fmap upd) i where + upd :: SrcSpanInfo -> SrcSpanInfo + upd sinfo = sinfo { + srcInfoSpan = (srcInfoSpan sinfo) { + srcSpanStartLine = s ^. positionLine, + srcSpanStartColumn = s ^. positionColumn, + srcSpanEndLine = e ^. positionLine, + srcSpanEndColumn = e ^. positionColumn }, + srcInfoPoints = [] } + +fileL :: (SrcInfo isrc, Data isrc) => Lens' isrc FilePath +fileL = lens g' s' where + g' = fileName + s' i f = set biplate f i + +-- | Get 'Symbol' as 'Name' +symbolNameL :: Lens' Symbol Name +symbolNameL = lens g' s' where + g' sym' = Qual () (symbolModule sym') (symbolName sym') + s' sym' (Qual _ m n) = sym' { symbolModule = m, symbolName = n } + s' sym' (UnQual _ n) = sym' { symbolName = n } + s' sym' _ = sym'
src/HsDev/Symbols/Resolve.hs view
@@ -1,176 +1,27 @@-{-# LANGUAGE GeneralizedNewtypeDeriving, TemplateHaskell #-}--module HsDev.Symbols.Resolve (- ResolveM(..),ResolvedTree, ResolvedModule(..), resolvedModule, resolvedScope, resolvedExports,- scopeModule, exportsModule, resolvedTopScope,- resolve, resolveOne, resolveModule, resolveImports, resolveImport,- mergeImported- ) where--import Control.Applicative ((<|>))-import Control.Arrow-import Control.Lens (makeLenses, view, preview, set, _Just, over, each)-import Control.Monad.Reader-import Control.Monad.State-import Data.Function (on)-import Data.List (sortBy, groupBy, delete)-import qualified Data.Map as M-import Data.Maybe (fromMaybe, listToMaybe, catMaybes)-import Data.Maybe.JustIf-import Data.Ord (comparing)-import qualified Data.Set as S-import Data.String (fromString)-import Data.Text (Text)--import HsDev.Database-import HsDev.Symbols-import HsDev.Symbols.Util-import HsDev.Util (uniqueBy)---- | Map from name to modules-type ModuleMap = M.Map Text [Module]---- | Resolve monad uses existing @Database@ and @ResolvedTree@ as state.-newtype ResolveM a = ResolveM { runResolveM :: ReaderT (Database, ModuleMap) (State ResolvedTree) a }- deriving (Functor, Applicative, Monad, MonadState ResolvedTree, MonadReader (Database, ModuleMap))---- | Tree of resolved modules-type ResolvedTree = Map ModuleId ResolvedModule---- | Module with declarations bringed to scope and with exported declarations-data ResolvedModule = ResolvedModule {- _resolvedModule :: Module,- _resolvedScope :: [Declaration],- _resolvedExports :: [Declaration] }--makeLenses ''ResolvedModule---- | Make @Module@ with scope declarations-scopeModule :: ResolvedModule -> Module-scopeModule r = set moduleDeclarations (view resolvedScope r) (view resolvedModule r)---- | Make @Module@ with exported only declarations-exportsModule :: ResolvedModule -> Module-exportsModule r = set moduleDeclarations (view resolvedExports r) (view resolvedModule r)---- | Get top-level scope-resolvedTopScope :: ResolvedModule -> [Declaration]-resolvedTopScope = filter isTop . view resolvedScope where- isTop :: Declaration -> Bool- isTop = any (not . view importIsQualified) . fromMaybe [] . view declarationImported---- | Resolve modules, function is not IO, so all file names must be canonicalized-resolve :: Traversable t => Database -> t Module -> t ResolvedModule-resolve db = flip evalState M.empty . flip runReaderT (db, m) . runResolveM . traverse resolveModule where- m :: ModuleMap- m = M.fromList $ map ((view moduleName . head) &&& id) $- groupBy ((==) `on` view moduleName) $- sortBy (comparing (view moduleName)) $ allModules db---- | Resolve one module-resolveOne :: Database -> Module -> ResolvedModule-resolveOne db = fromMaybe (error "Resolve: impossible happened") . resolve db . Just---- | Resolve module-resolveModule :: Module -> ResolveM ResolvedModule-resolveModule m = gets (M.lookup $ view moduleId m) >>= maybe resolveModule' return where- resolveModule' = save $ case view moduleLocation m of- InstalledModule {} -> return ResolvedModule {- _resolvedModule = m,- _resolvedScope = map setSelfDefined $ view moduleDeclarations m,- _resolvedExports = map setSelfDefined $ view moduleDeclarations m }- _ -> do- scope' <-- liftM (thisDecls ++) .- resolveImports m .- (import_ (fromString "Prelude") :) .- view moduleImports $ m- let- exported' = case view moduleExports m of- Nothing -> thisDecls- Just exports' -> unique $ catMaybes $ mexported <$> scope' <*> exports'- return $ ResolvedModule m (sortDeclarations scope') (sortDeclarations exported')- thisDecls :: [Declaration]- thisDecls = map (selfDefined . selfImport) $ view moduleDeclarations m- setSelfDefined :: Declaration -> Declaration- setSelfDefined =- over (declaration . localDeclarations . each . declarationDefined) (<|> Just (view moduleId m)) .- over declarationDefined (<|> Just (view moduleId m))- selfDefined :: Declaration -> Declaration- selfDefined = set declarationDefined (Just $ view moduleId m)- selfImport :: Declaration -> Declaration- selfImport = set declarationImported (Just [import_ $ view moduleName m])- save :: ResolveM ResolvedModule -> ResolveM ResolvedModule- save act = do- rm <- act- modify $ M.insert (view (resolvedModule . moduleId) rm) rm- return rm- unique :: [Declaration] -> [Declaration]- unique = uniqueBy declId- declId :: Declaration -> (Text, Maybe ModuleId)- declId = view declarationName &&& view declarationDefined- mexported decl' e' = decl' `justIf` exported decl' e'---- | Bring declarations into scope by imports-resolveImports :: Module -> [Import] -> ResolveM [Declaration]-resolveImports m is = do- db <- asks fst- let- deps = maybe S.empty S.fromList $ do- f <- preview (moduleLocation . moduleFile) m- p <- preview (moduleLocation . moduleProject . _Just) m- p' <- refineProject db p- return $ delete (view projectName p') $ concatMap (view infoDepends) $ fileTargets p' f- liftM (mergeImported . concat) $ mapM (resolveImport deps m) is---- | Bring declarations into scope, first parameter is set of visible packages-resolveImport :: S.Set String -> Module -> Import -> ResolveM [Declaration]-resolveImport deps m i = liftM (map $ setImport i) resolveImport' where- resolveImport' :: ResolveM [Declaration]- resolveImport' = do- ms <- case view moduleLocation m of- FileModule file proj -> do- db <- asks fst- let- proj' = proj >>= refineProject db- case proj' of- Nothing -> selectImport i [- inFile $ importedModulePath (view moduleName m) file (view importModuleName i),- installed]- Just p -> selectImport i [- inProject p,- inDeps]- InstalledModule pdb _ _ -> selectImport i [inPackageDb pdb]- ModuleSource _ -> selectImport i [installed]- fromMaybe [] <$> traverse (liftM (filterImportList . view resolvedExports) . resolveModule) ms- setImport :: Import -> Declaration -> Declaration- setImport i' = set declarationImported (Just [i'])- selectImport :: Import -> [ModuleId -> Bool] -> ResolveM (Maybe Module)- selectImport i' fs = do- modsMap <- asks snd- let- mods = fromMaybe [] $ M.lookup (view importModuleName i') modsMap- return $- listToMaybe $- newestPackage $- fromMaybe [] $- listToMaybe $ dropWhile null- [filter (f . view moduleId) mods | f <- fs]- filterImportList :: [Declaration] -> [Declaration]- filterImportList = case view importList i of- Nothing -> id- Just il -> filter (`imported` il)- inDeps = maybe False (`S.member` deps) . preview (moduleIdLocation . modulePackage . _Just . packageName)---- | Merge imported declarations-mergeImported :: [Declaration] -> [Declaration]-mergeImported =- map merge' .- groupBy ((==) `on` declId) .- sortBy (comparing declId)- where- declId :: Declaration -> (Text, Maybe ModuleId)- declId = view declarationName &&& view declarationDefined- merge' :: [Declaration] -> Declaration- merge' [] = error "mergeImported: impossible"- merge' ds@(d:_) = set declarationImported (mconcat $ map (view declarationImported) ds) d+{-# LANGUAGE RankNTypes #-} + +module HsDev.Symbols.Resolve ( + RefineTable, refineTable, refineSymbol, refineSymbols, + symbolUniqId + ) where + +import Control.Lens +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe) +import Data.Text (Text) + +import HsDev.Symbols + +type RefineTable = M.Map (Text, Text, SymbolInfo) Symbol + +refineTable :: [Symbol] -> RefineTable +refineTable syms = M.fromList [(symbolUniqId s, s) | s <- syms] + +refineSymbol :: RefineTable -> Symbol -> Symbol +refineSymbol tbl s = fromMaybe s $ M.lookup (symbolUniqId s) tbl + +refineSymbols :: RefineTable -> Module -> Module +refineSymbols tbl = over moduleSymbols (refineSymbol tbl) + +symbolUniqId :: Symbol -> (Text, Text, SymbolInfo) +symbolUniqId s = (view (symbolId . symbolName) s, view (symbolId . symbolModule . moduleName) s, nullifyInfo $ view symbolInfo s)
src/HsDev/Symbols/Types.hs view
@@ -1,620 +1,529 @@-{-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Symbols.Types (- ThingPart(..),- Export(..), exportQualified, exportName, exportPart, exportModule,- ImportSpec(..), importSpecName, importSpecPart,- ImportList(..), hidingList, importSpecs,- Import(..), importModuleName, importIsQualified, importAs, importList, importPosition,- ModuleId(..), moduleIdName, moduleIdLocation,- Module(..), moduleName, moduleDocs, moduleLocation, moduleExports, moduleImports, moduleDeclarations, moduleContents, moduleId,- Declaration(..), declarationName, declarationDefined, declarationImported, declarationDocs, declarationPosition, declaration, minimalDecl,- TypeInfo(..), typeInfoContext, typeInfoArgs, typeInfoDefinition, typeInfoFunctions, showTypeInfo,- DeclarationInfo(..), functionType, localDeclarations, related, typeInfo, declarationInfo, declarationTypeCtor, declarationTypeName,- ModuleDeclaration(..), declarationModuleId, moduleDeclaration,- ExportedDeclaration(..), exportedBy, exportedDeclaration,- Inspection(..), inspectionAt, inspectionOpts,- Inspected(..), inspection, inspectedId, inspectionTags, inspectionResult, noTags,- ModuleTag(..),- InspectedModule, notInspected,-- module HsDev.PackageDb,- module HsDev.Project,- module HsDev.Symbols.Class,- module HsDev.Symbols.Documented- ) where--import Control.Applicative-import Control.Arrow-import Control.Lens (makeLenses, view, set, Simple, Lens, Lens', lens)-import Control.Monad-import Control.DeepSeq (NFData(..))-import Data.Aeson-import Data.List (intercalate)-import Data.Maybe (fromMaybe)-import Data.Function-import Data.Ord-import Data.Text (Text, unpack)-import qualified Data.Text as T-import Data.Set (Set)-import qualified Data.Set as S-import Data.Time.Clock.POSIX (POSIXTime)--import HsDev.PackageDb-import HsDev.Project-import HsDev.Symbols.Class-import HsDev.Symbols.Documented-import HsDev.Types-import HsDev.Util (tab, tabs, (.::), (.::?), (.::?!), noNulls)---- | What to export/import for data/class etc-data ThingPart = ThingNothing | ThingAll | ThingWith [Text] deriving (Eq, Ord)--instance NFData ThingPart where- rnf ThingNothing = ()- rnf ThingAll = ()- rnf (ThingWith ns) = rnf ns--instance Show ThingPart where- show ThingNothing = ""- show ThingAll = "(..)"- show (ThingWith ns) = "(" ++ intercalate ", " (map unpack ns) ++ ")"--instance ToJSON ThingPart where- toJSON ThingNothing = toJSON ("nothing" :: String)- toJSON ThingAll = toJSON ("all" :: String)- toJSON (ThingWith ns) = object [- "with" .= ns]--instance FromJSON ThingPart where- parseJSON v = parse' <|> parseWith v where- parse' = do- s <- parseJSON v- mplus- (guard (s == ("nothing" :: String)) >> return ThingNothing)- (guard (s == ("all" :: String)) >> return ThingAll)- parseWith = withObject "export part" $ \v' -> ThingWith <$> v' .:: "with"---- | Module export-data Export =- ExportName {- _exportQualified :: Maybe Text,- _exportName :: Text,- _exportPart :: ThingPart } |- ExportModule { _exportModule :: Text }- deriving (Eq, Ord)--instance NFData Export where- rnf (ExportName q n w) = rnf q `seq` rnf n `seq` rnf w- rnf (ExportModule m) = rnf m--instance Show Export where- show (ExportName Nothing n w) = unpack n ++ show w- show (ExportName (Just q) n w) = unpack q ++ "." ++ unpack n ++ show w- show (ExportModule m) = "module " ++ unpack m--instance ToJSON Export where- toJSON (ExportName q n w) = object ["module" .= q, "name" .= n, "part" .= w]- toJSON (ExportModule m) = object ["module" .= m]--instance FromJSON Export where- parseJSON = withObject "export" $ \v ->- (ExportName <$> (v .:: "module") <*> (v .:: "name") <*> (v .:: "part")) <|>- (ExportModule <$> (v .:: "module"))---- | Import spec-data ImportSpec = ImportSpec {- _importSpecName :: Text,- _importSpecPart :: ThingPart }- deriving (Eq, Ord)--instance NFData ImportSpec where- rnf (ImportSpec n p) = rnf n `seq` rnf p--instance Show ImportSpec where- show (ImportSpec n p) = unpack n ++ show p--instance ToJSON ImportSpec where- toJSON (ImportSpec n p) = object ["name" .= n, "part" .= p]--instance FromJSON ImportSpec where- parseJSON = withObject "import-spec" $ \v -> ImportSpec <$> (v .:: "name") <*> (v .:: "part")---- | Import list-data ImportList = ImportList {- _hidingList :: Bool,- _importSpecs :: [ImportSpec] }- deriving (Eq, Ord)--instance NFData ImportList where- rnf (ImportList h ls) = rnf h `seq` rnf ls--instance Show ImportList where- show (ImportList h ls) = (if h then ("hiding " ++) else id) $ "(" ++ intercalate ", " (map show ls) ++ ")"--instance ToJSON ImportList where- toJSON (ImportList h ls) = object [- "hiding" .= h,- "specs" .= ls]--instance FromJSON ImportList where- parseJSON = withObject "import-list" $ \v -> ImportList <$>- v .:: "hiding" <*>- v .:: "specs"---- | Module import-data Import = Import {- _importModuleName :: Text,- _importIsQualified :: Bool,- _importAs :: Maybe Text,- _importList :: Maybe ImportList,- _importPosition :: Maybe Position }- deriving (Eq, Ord)--instance NFData Import where- rnf (Import m q a il l) = rnf m `seq` rnf q `seq` rnf a `seq` rnf il `seq` rnf l--instance Show Import where- show i = concat [- "import ",- if _importIsQualified i then "qualified " else "",- unpack $ _importModuleName i,- maybe "" ((" as " ++) . unpack) (_importAs i),- maybe "" ((" " ++) . show) (_importList i)]--instance ToJSON Import where- toJSON i = object $ noNulls [- "name" .= _importModuleName i,- "qualified" .= _importIsQualified i,- "as" .= _importAs i,- "import-list" .= _importList i,- "pos" .= _importPosition i]--instance FromJSON Import where- parseJSON = withObject "import" $ \v -> Import <$>- v .:: "name" <*>- v .:: "qualified" <*>- v .::? "as" <*>- v .::? "import-list" <*>- v .::? "pos"---- | Module id-data ModuleId = ModuleId {- _moduleIdName :: Text,- _moduleIdLocation :: ModuleLocation }- deriving (Eq, Ord)--instance NFData ModuleId where- rnf (ModuleId n l) = rnf n `seq` rnf l--instance Show ModuleId where- show (ModuleId n l) = "module " ++ unpack n ++ " from " ++ show l--instance ToJSON ModuleId where- toJSON m = object [- "name" .= _moduleIdName m,- "location" .= _moduleIdLocation m]--instance FromJSON ModuleId where- parseJSON = withObject "module id" $ \v -> ModuleId <$>- v .:: "name" <*>- v .:: "location"---- | Module-data Module = Module {- _moduleName :: Text,- _moduleDocs :: Maybe Text,- _moduleLocation :: ModuleLocation,- _moduleExports :: Maybe [Export],- _moduleImports :: [Import],- _moduleDeclarations :: [Declaration] }- deriving (Ord)--instance ToJSON Module where- toJSON m = object $ noNulls [- "name" .= _moduleName m,- "docs" .= _moduleDocs m,- "location" .= _moduleLocation m,- "exports" .= _moduleExports m,- "imports" .= _moduleImports m,- "declarations" .= _moduleDeclarations m]--instance FromJSON Module where- parseJSON = withObject "module" $ \v -> Module <$>- v .:: "name" <*>- v .::? "docs" <*>- v .:: "location" <*>- v .::? "exports" <*>- v .::?! "imports" <*>- v .::?! "declarations"--instance NFData Module where- rnf (Module n d s e i ds) = rnf n `seq` rnf d `seq` rnf s `seq` rnf e `seq` rnf i `seq` rnf ds--instance Eq Module where- l == r = _moduleName l == _moduleName r && _moduleLocation l == _moduleLocation r--instance Show Module where- show m = unlines $ filter (not . null) [- "module " ++ unpack (_moduleName m),- "\tlocation: " ++ show (_moduleLocation m),- "\texports: " ++ maybe "*" (intercalate ", " . map show) (_moduleExports m),- "\timports:",- unlines $ map (tab 2 . show) $ _moduleImports m,- "\tdeclarations:",- unlines $ map (tabs 2 . show) $ _moduleDeclarations m,- maybe "" (("\tdocs: " ++) . unpack) (_moduleDocs m)]--moduleId :: Simple Lens Module ModuleId-moduleId = lens- (uncurry ModuleId . (_moduleName &&& _moduleLocation))- (\m mi -> m { _moduleName = _moduleIdName mi, _moduleLocation = _moduleIdLocation mi })---- | Module contents-moduleContents :: Module -> [String]-moduleContents = map showDecl . _moduleDeclarations where- showDecl d = brief d ++ maybe "" ((" -- " ++) . unpack) (_declarationDocs d)---- | Declaration-data Declaration = Declaration {- _declarationName :: Text,- _declarationDefined :: Maybe ModuleId, -- ^ Where declaration defined, @Nothing@ if here- _declarationImported :: Maybe [Import], -- ^ Declaration imported with. @Nothing@ if unknown (cabal modules) or here (source file)- _declarationDocs :: Maybe Text,- _declarationPosition :: Maybe Position,- _declaration :: DeclarationInfo }- deriving (Eq, Ord)--instance NFData Declaration where- rnf (Declaration n def is d l x) = rnf n `seq` rnf def `seq` rnf is `seq` rnf d `seq` rnf l `seq` rnf x--instance Show Declaration where- show d = unlines $ filter (not . null) [- brief d,- maybe "" (("\tdocs: " ++) . unpack) $ _declarationDocs d,- maybe "" (("\tdefined in: " ++) . show) $ _declarationDefined d,- maybe "" (("\tlocation: " ++ ) . show) $ _declarationPosition d]--instance ToJSON Declaration where- toJSON d = object $ noNulls [- "name" .= _declarationName d,- "defined" .= _declarationDefined d,- "imported" .= _declarationImported d,- "docs" .= _declarationDocs d,- "pos" .= _declarationPosition d,- "decl" .= _declaration d]--instance FromJSON Declaration where- parseJSON = withObject "declaration" $ \v -> Declaration <$>- v .:: "name" <*>- v .::? "defined" <*>- v .::? "imported" <*>- v .::? "docs" <*>- v .::? "pos" <*>- v .:: "decl"---- | Minimal declaration info without defined, docs and position-minimalDecl :: Lens' Declaration Declaration-minimalDecl = lens to' from' where- to' :: Declaration -> Declaration- to' decl' = decl' { _declarationDefined = Nothing, _declarationDocs = Nothing, _declarationPosition = Nothing }- from' :: Declaration -> Declaration -> Declaration- from' decl' mdecl = decl' { _declarationName = _declarationName mdecl, _declarationImported = _declarationImported mdecl, _declaration = _declaration mdecl }---- | Common info for type, newtype, data and class-data TypeInfo = TypeInfo {- _typeInfoContext :: Maybe Text, -- FIXME: Why not list of contexts?- _typeInfoArgs :: [Text],- _typeInfoDefinition :: Maybe Text,- _typeInfoFunctions :: [Text] }- deriving (Eq, Ord, Read, Show)--instance NFData TypeInfo where- rnf (TypeInfo c a d f) = rnf c `seq` rnf a `seq` rnf d `seq` rnf f--instance ToJSON TypeInfo where- toJSON t = object $ noNulls [- "ctx" .= _typeInfoContext t,- "args" .= _typeInfoArgs t,- "def" .= _typeInfoDefinition t,- "funs" .= _typeInfoFunctions t]--instance FromJSON TypeInfo where- parseJSON = withObject "type info" $ \v -> TypeInfo <$>- v .::? "ctx" <*>- v .::?! "args" <*>- v .::? "def" <*>- v .::?! "funs"--showTypeInfo :: TypeInfo -> String -> String -> String-showTypeInfo ti pre name = concat [- pre,- maybe "" ((++ " =>") . unpack) (_typeInfoContext ti), " ",- name, " ",- unwords (map unpack $ _typeInfoArgs ti),- maybe "" ((" = " ++) . unpack) (_typeInfoDefinition ti)]---- | Declaration info-data DeclarationInfo =- Function { _functionType :: Maybe Text, _localDeclarations :: [Declaration], _related :: Maybe Text } |- Type { _typeInfo :: TypeInfo } |- NewType { _typeInfo :: TypeInfo } |- Data { _typeInfo :: TypeInfo } |- Class { _typeInfo :: TypeInfo }- deriving (Ord)---- | Get function type of type info-declarationInfo :: DeclarationInfo -> Either (Maybe Text, [Declaration], Maybe Text) TypeInfo-declarationInfo (Function t ds r) = Left (t, ds, r)-declarationInfo (Type ti) = Right ti-declarationInfo (NewType ti) = Right ti-declarationInfo (Data ti) = Right ti-declarationInfo (Class ti) = Right ti--declarationTypeCtor :: String -> TypeInfo -> DeclarationInfo-declarationTypeCtor "type" = Type-declarationTypeCtor "newtype" = NewType-declarationTypeCtor "data" = Data-declarationTypeCtor "class" = Class-declarationTypeCtor _ = error "Invalid type constructor name"--declarationTypeName :: DeclarationInfo -> Maybe String-declarationTypeName (Type _) = Just "type"-declarationTypeName (NewType _) = Just "newtype"-declarationTypeName (Data _) = Just "data"-declarationTypeName (Class _) = Just "class"-declarationTypeName _ = Nothing--instance NFData DeclarationInfo where- rnf (Function f ds r) = rnf f `seq` rnf ds `seq` rnf r- rnf (Type i) = rnf i- rnf (NewType i) = rnf i- rnf (Data i) = rnf i- rnf (Class i) = rnf i--instance Eq DeclarationInfo where- (Function l lds lr) == (Function r rds rr) = l == r && lds == rds && lr == rr- (Type _) == (Type _) = True- (NewType _) == (NewType _) = True- (Data _) == (Data _) = True- (Class _) == (Class _) = True- _ == _ = False--instance ToJSON DeclarationInfo where- toJSON i = case declarationInfo i of- Left (t, ds, r) -> object $ noNulls ["what" .= ("function" :: String), "type" .= t, "locals" .= ds, "related" .= r]- Right ti -> object ["what" .= declarationTypeName i, "info" .= ti]--instance FromJSON DeclarationInfo where- parseJSON = withObject "declaration info" $ \v -> do- w <- fmap (id :: String -> String) $ v .:: "what"- if w == "function"- then Function <$> v .::? "type" <*> v .::?! "locals" <*> v .::? "related"- else declarationTypeCtor w <$> v .:: "info"---- | Symbol in context of some module-data ModuleDeclaration = ModuleDeclaration {- _declarationModuleId :: ModuleId,- _moduleDeclaration :: Declaration }- deriving (Eq, Ord)--instance NFData ModuleDeclaration where- rnf (ModuleDeclaration m s) = rnf m `seq` rnf s--instance Show ModuleDeclaration where- show (ModuleDeclaration m s) = unlines $ filter (not . null) [- show s,- "\tmodule: " ++ show (_moduleIdLocation m)]--instance ToJSON ModuleDeclaration where- toJSON d = object [- "module-id" .= _declarationModuleId d,- "declaration" .= _moduleDeclaration d]--instance FromJSON ModuleDeclaration where- parseJSON = withObject "module declaration" $ \v -> ModuleDeclaration <$>- v .:: "module-id" <*>- v .:: "declaration"---- | Symbol exported with-data ExportedDeclaration = ExportedDeclaration {- _exportedBy :: [ModuleId],- _exportedDeclaration :: Declaration }- deriving (Eq, Ord)--instance NFData ExportedDeclaration where- rnf (ExportedDeclaration m s) = rnf m `seq` rnf s--instance Show ExportedDeclaration where- show (ExportedDeclaration m s) = unlines $ filter (not . null) [- show s,- "\tmodules: " ++ intercalate ", " (map (show . _moduleIdLocation) m)]--instance ToJSON ExportedDeclaration where- toJSON d = object [- "exported-by" .= _exportedBy d,- "declaration" .= _exportedDeclaration d]--instance FromJSON ExportedDeclaration where- parseJSON = withObject "exported declaration" $ \v -> ExportedDeclaration <$>- v .:: "exported-by" <*>- v .:: "declaration"---- | Inspection data-data Inspection =- -- | No inspection- InspectionNone |- -- | Time and flags of inspection- InspectionAt {- _inspectionAt :: POSIXTime,- _inspectionOpts :: [String] }- deriving (Eq, Ord)--instance NFData Inspection where- rnf InspectionNone = ()- rnf (InspectionAt t fs) = rnf t `seq` rnf fs--instance Show Inspection where- show InspectionNone = "none"- show (InspectionAt tm fs) = "mtime " ++ show tm ++ ", flags [" ++ intercalate ", " fs ++ "]"--instance Read POSIXTime where- readsPrec i = map (first (fromIntegral :: Integer -> POSIXTime)) . readsPrec i--instance Monoid Inspection where- mempty = InspectionNone- mappend InspectionNone r = r- mappend l InspectionNone = l- mappend (InspectionAt ltm lopts) (InspectionAt rtm ropts)- | ltm >= rtm = InspectionAt ltm lopts- | otherwise = InspectionAt rtm ropts--instance ToJSON Inspection where- toJSON InspectionNone = object ["inspected" .= False]- toJSON (InspectionAt tm fs) = object [- "mtime" .= (floor tm :: Integer),- "flags" .= fs]--instance FromJSON Inspection where- parseJSON = withObject "inspection" $ \v ->- ((const InspectionNone :: Bool -> Inspection) <$> v .:: "inspected") <|>- (InspectionAt <$> (fromInteger <$> v .:: "mtime") <*> (v .:: "flags"))---- | Inspected entity-data Inspected i t a = Inspected {- _inspection :: Inspection,- _inspectedId :: i,- _inspectionTags :: Set t,- _inspectionResult :: Either HsDevError a }--inspectedTup :: Inspected i t a -> (Inspection, i, Set t, Maybe a)-inspectedTup (Inspected insp i tags res) = (insp, i, tags, either (const Nothing) Just res)--instance (Eq i, Eq t, Eq a) => Eq (Inspected i t a) where- (==) = (==) `on` inspectedTup--instance (Ord i, Ord t, Ord a) => Ord (Inspected i t a) where- compare = comparing inspectedTup--instance Functor (Inspected i t) where- fmap f insp = insp {- _inspectionResult = fmap f (_inspectionResult insp) }--instance Foldable (Inspected i t) where- foldMap f = either mempty f . _inspectionResult--instance Traversable (Inspected i t) where- traverse f (Inspected insp i ts r) = Inspected insp i ts <$> either (pure . Left) (liftA Right . f) r--instance (NFData i, NFData t, NFData a) => NFData (Inspected i t a) where- rnf (Inspected t i ts r) = rnf t `seq` rnf i `seq` rnf ts `seq` rnf r---- | Empty tags-noTags :: Set t-noTags = S.empty--data ModuleTag = InferredTypesTag | RefinedDocsTag deriving (Eq, Ord, Read, Show, Enum, Bounded)--instance NFData ModuleTag where- rnf InferredTypesTag = ()- rnf RefinedDocsTag = ()--instance ToJSON ModuleTag where- toJSON InferredTypesTag = toJSON ("types" :: String)- toJSON RefinedDocsTag = toJSON ("docs" :: String)--instance FromJSON ModuleTag where- parseJSON = withText "module-tag" $ \txt -> msum [- guard (txt == "types") >> return InferredTypesTag,- guard (txt == "docs") >> return RefinedDocsTag]---- | Inspected module-type InspectedModule = Inspected ModuleLocation ModuleTag Module--instance Show InspectedModule where- show (Inspected i mi ts m) = unlines [either showError show m, "\tinspected: " ++ show i, "\ttags: " ++ intercalate ", " (map show $ S.toList ts)] where- showError :: HsDevError -> String- showError e = unlines $ ("\terror: " ++ show e) : case mi of- FileModule f p -> ["file: " ++ f, "project: " ++ maybe "" (view projectPath) p]- InstalledModule c p n -> ["cabal: " ++ show c, "package: " ++ maybe "" show p, "name: " ++ n]- ModuleSource src -> ["source: " ++ fromMaybe "" src]--instance ToJSON InspectedModule where- toJSON im = object [- "inspection" .= _inspection im,- "location" .= _inspectedId im,- "tags" .= S.toList (_inspectionTags im),- either ("error" .=) ("module" .=) (_inspectionResult im)]--instance FromJSON InspectedModule where- parseJSON = withObject "inspected module" $ \v -> Inspected <$>- v .:: "inspection" <*>- v .:: "location" <*>- (S.fromList <$> (v .::?! "tags")) <*>- ((Left <$> v .:: "error") <|> (Right <$> v .:: "module"))--notInspected :: ModuleLocation -> InspectedModule-notInspected mloc = Inspected mempty mloc noTags (Left $ NotInspected mloc)--instance Symbol Module where- symbolName = _moduleName- symbolQualifiedName = _moduleName- symbolDocs = _moduleDocs- symbolLocation m = Location (_moduleLocation m) Nothing--instance Symbol ModuleId where- symbolName = _moduleIdName- symbolQualifiedName = _moduleIdName- symbolDocs = const Nothing- symbolLocation m = Location (_moduleIdLocation m) Nothing--instance Symbol Declaration where- symbolName = _declarationName- symbolQualifiedName = _declarationName- symbolDocs = _declarationDocs- symbolLocation d = Location (ModuleSource Nothing) (_declarationPosition d)--instance Symbol ModuleDeclaration where- symbolName = _declarationName . _moduleDeclaration- symbolQualifiedName d = qualifiedName (_declarationModuleId d) (_moduleDeclaration d) where- qualifiedName :: ModuleId -> Declaration -> Text- qualifiedName m' d' = T.concat [_moduleIdName m', ".", _declarationName d']- symbolDocs = _declarationDocs . _moduleDeclaration- symbolLocation d = set locationPosition (_declarationPosition $ _moduleDeclaration d)- (symbolLocation . _declarationModuleId $ d)--instance Documented ModuleId where- brief m = unpack (_moduleIdName m) ++ " in " ++ show (_moduleIdLocation m)--instance Documented Module where- brief m = unpack (_moduleName m) ++ " in " ++ show (_moduleLocation m)- detailed m = unlines $ header ++ docs ++ cts where- header = [brief m, ""]- docs = maybe [] (return . unpack) $ _moduleDocs m- cts = moduleContents m--instance Documented Declaration where- brief d = case declarationInfo $ _declaration d of- Left (f, _, _) -> name ++ maybe "" ((" :: " ++) . unpack) f- Right ti -> showTypeInfo ti (fromMaybe err $ declarationTypeName $ _declaration d) name- where- name = unpack $ _declarationName d- err = error "Impossible happened: declarationTypeName"--instance Documented ModuleDeclaration where- brief = brief . _moduleDeclaration--makeLenses ''Export-makeLenses ''ImportSpec-makeLenses ''ImportList-makeLenses ''Import-makeLenses ''ModuleId-makeLenses ''DeclarationInfo-makeLenses ''TypeInfo-makeLenses ''Declaration-makeLenses ''Module-makeLenses ''ModuleDeclaration-makeLenses ''ExportedDeclaration-makeLenses ''Inspection-makeLenses ''Inspected+{-# LANGUAGE CPP, TemplateHaskell, TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Symbols.Types ( + Module(..), moduleSymbols, exportedSymbols, scopeSymbols, fixitiesMap, moduleFixities, moduleId, moduleDocs, moduleImports, moduleExports, moduleScope, moduleSource, + Symbol(..), symbolId, symbolDocs, symbolPosition, symbolInfo, + SymbolInfo(..), functionType, parentClass, parentType, selectorConstructors, typeArgs, typeContext, familyAssociate, symbolType, patternType, patternConstructor, + ScopeSymbol(..), scopeQualifier, scopeSymbol, + SymbolUsage(..), symbolUsed, symbolUsedIn, symbolUsedPosition, + infoOf, nullifyInfo, + Inspection(..), inspectionAt, inspectionOpts, fresh, Inspected(..), inspection, inspectedKey, inspectionTags, inspectionResult, inspected, + inspectedTup, noTags, tag, ModuleTag(..), InspectedModule, notInspected, + + module HsDev.PackageDb.Types, + module HsDev.Project, + module HsDev.Symbols.Name, + module HsDev.Symbols.Class, + module HsDev.Symbols.Location, + module HsDev.Symbols.Documented + ) where + +import Control.Arrow +import Control.Applicative +import Control.Lens hiding ((.=)) +import Control.Monad +import Control.DeepSeq (NFData(..)) +import Data.Aeson +import Data.Aeson.Types (Pair, Parser) +import Data.List (intercalate) +import Data.Maybe (catMaybes) +import Data.Monoid (Any(..)) +import Data.Function +import Data.Ord +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Text (Text) +import qualified Data.Text as T +import Data.Set (Set) +import qualified Data.Set as S +import Data.Time.Clock.POSIX (POSIXTime) +import Language.Haskell.Exts (QName(..), ModuleName(..), Boxed(..), SpecialCon(..), Fixity(..), Assoc(..)) +import qualified Language.Haskell.Exts as Exts (Name(..)) +import Text.Format + +import Control.Apply.Util (chain) +import HsDev.PackageDb.Types +import HsDev.Project +import HsDev.Symbols.Name +import HsDev.Symbols.Class +import HsDev.Symbols.Location +import HsDev.Symbols.Documented +import HsDev.Symbols.Parsed +import HsDev.Types +import HsDev.Util ((.::), (.::?), (.::?!), noNulls, objectUnion) +import System.Directory.Paths + +instance NFData l => NFData (ModuleName l) where + rnf (ModuleName l n) = rnf l `seq` rnf n + +instance NFData l => NFData (Exts.Name l) where + rnf (Exts.Ident l s) = rnf l `seq` rnf s + rnf (Exts.Symbol l s) = rnf l `seq` rnf s + +instance NFData Boxed where + rnf Boxed = () + rnf Unboxed = () + +instance NFData l => NFData (SpecialCon l) where + rnf (UnitCon l) = rnf l + rnf (ListCon l) = rnf l + rnf (FunCon l) = rnf l + rnf (TupleCon l b i) = rnf l `seq` rnf b `seq` rnf i + rnf (Cons l) = rnf l + rnf (UnboxedSingleCon l) = rnf l +#if MIN_VERSION_haskell_src_exts(1,20,0) + rnf (ExprHole l) = rnf l +#endif + +instance NFData l => NFData (QName l) where + rnf (Qual l m n) = rnf l `seq` rnf m `seq` rnf n + rnf (UnQual l n) = rnf l `seq` rnf n + rnf (Special l s) = rnf l `seq` rnf s + +-- | Module +data Module = Module { + _moduleId :: ModuleId, + _moduleDocs :: Maybe Text, + _moduleImports :: [Text], -- list of module names imported + _moduleExports :: [Symbol], -- exported module symbols + _moduleFixities :: [Fixity], -- fixities of operators + _moduleScope :: Map Name [Symbol], -- symbols in scope, only for source modules + _moduleSource :: Maybe Parsed } -- source of module + +-- | Make each symbol appear only once +moduleSymbols :: Traversal' Module Symbol +moduleSymbols f m = getBack <$> (each . _1) f revList where + revList = M.toList $ M.unionsWith mappend $ concat [ + [M.singleton sym ([], Any True) | sym <- _moduleExports m], + [M.singleton sym ([nm], Any False) | (nm, syms) <- M.toList (_moduleScope m), sym <- syms]] + getBack syms = m { + _moduleExports = [sym' | (sym', (_, Any True)) <- syms], + _moduleScope = M.unionsWith (++) [M.singleton n [sym'] | (sym', (ns, _)) <- syms, n <- ns] } + +exportedSymbols :: Traversal' Module Symbol +exportedSymbols f m = (\e -> m { _moduleExports = e }) <$> traverse f (_moduleExports m) + +scopeSymbols :: Traversal' Module (Symbol, [Name]) +scopeSymbols f m = (\s -> m { _moduleScope = invMap s }) <$> traverse f (M.toList . invMap . M.toList $ _moduleScope m) where + invMap :: Ord b => [(a, [b])] -> Map b [a] + invMap es = M.unionsWith (++) [M.singleton v [k] | (k, vs) <- es, v <- vs] + +fixitiesMap :: Lens' Module (Map Name Fixity) +fixitiesMap = lens g' s' where + g' m = mconcat [M.singleton n f | f@(Fixity _ _ n) <- _moduleFixities m] + s' m m' = m { _moduleFixities = M.elems m' } + +instance ToJSON (Assoc ()) where + toJSON (AssocNone _) = toJSON ("none" :: String) + toJSON (AssocLeft _) = toJSON ("left" :: String) + toJSON (AssocRight _) = toJSON ("right" :: String) + +instance FromJSON (Assoc ()) where + parseJSON = withText "assoc" $ \txt -> msum [ + guard (txt == "none") >> return (AssocNone ()), + guard (txt == "left") >> return (AssocLeft ()), + guard (txt == "right") >> return (AssocRight ())] + +instance ToJSON Fixity where + toJSON (Fixity assoc pr n) = object $ noNulls [ + "assoc" .= assoc, + "prior" .= pr, + "name" .= fromName n] + +instance FromJSON Fixity where + parseJSON = withObject "fixity" $ \v -> Fixity <$> + v .:: "assoc" <*> + v .:: "prior" <*> + (toName <$> v .:: "name") + +instance ToJSON Module where + toJSON m = object $ noNulls [ + "id" .= _moduleId m, + "docs" .= _moduleDocs m, + "exports" .= _moduleExports m, + "fixities" .= _moduleFixities m] + +instance FromJSON Module where + parseJSON = withObject "module" $ \v -> Module <$> + v .:: "id" <*> + v .::? "docs" <*> + pure mempty <*> + v .::?! "exports" <*> + v .::?! "fixities" <*> + pure mempty <*> + pure Nothing + +instance NFData (Assoc ()) where + rnf (AssocNone _) = () + rnf (AssocLeft _) = () + rnf (AssocRight _) = () + +instance NFData Fixity where + rnf (Fixity assoc pr n) = rnf assoc `seq` rnf pr `seq` rnf n + +instance NFData Module where + rnf (Module i d is e fs s msrc) = msrc `seq` rnf i `seq` rnf d `seq` rnf is `seq` rnf e `seq` rnf fs `seq` rnf s + +instance Eq Module where + l == r = _moduleId l == _moduleId r + +instance Ord Module where + compare l r = compare (_moduleId l) (_moduleId r) + +instance Show Module where + show = show . _moduleId + +data Symbol = Symbol { + _symbolId :: SymbolId, + _symbolDocs :: Maybe Text, + _symbolPosition :: Maybe Position, + _symbolInfo :: SymbolInfo } + +instance Eq Symbol where + l == r = (_symbolId l, symbolType l) == (_symbolId r, symbolType r) + +instance Ord Symbol where + compare l r = compare (_symbolId l, symbolType l) (_symbolId r, symbolType r) + +instance NFData Symbol where + rnf (Symbol i d l info) = rnf i `seq` rnf d `seq` rnf l `seq` rnf info + +instance Show Symbol where + show = show . _symbolId + +instance ToJSON Symbol where + toJSON s = object $ noNulls [ + "id" .= _symbolId s, + "docs" .= _symbolDocs s, + "pos" .= _symbolPosition s, + "info" .= _symbolInfo s] + +instance FromJSON Symbol where + parseJSON = withObject "symbol" $ \v -> Symbol <$> + v .:: "id" <*> + v .::? "docs" <*> + v .::? "pos" <*> + v .:: "info" + +data SymbolInfo = + Function { _functionType :: Maybe Text } | + Method { _functionType :: Maybe Text, _parentClass :: Text } | + Selector { _functionType :: Maybe Text, _parentType :: Text, _selectorConstructors :: [Text] } | + Constructor { _typeArgs :: [Text], _parentType :: Text } | + Type { _typeArgs :: [Text], _typeContext :: [Text] } | + NewType { _typeArgs :: [Text], _typeContext :: [Text] } | + Data { _typeArgs :: [Text], _typeContext :: [Text] } | + Class { _typeArgs :: [Text], _typeContext :: [Text] } | + TypeFam { _typeArgs :: [Text], _typeContext :: [Text], _familyAssociate :: Maybe Text } | + DataFam { _typeArgs :: [Text], _typeContext :: [Text], _familyAssociate :: Maybe Text } | + PatConstructor { _typeArgs :: [Text], _patternType :: Maybe Text } | + PatSelector { _functionType :: Maybe Text, _patternType :: Maybe Text, _patternConstructor :: Text } + deriving (Eq, Ord, Read, Show) + +instance NFData SymbolInfo where + rnf (Function ft) = rnf ft + rnf (Method ft cls) = rnf ft `seq` rnf cls + rnf (Selector ft t cs) = rnf ft `seq` rnf t `seq` rnf cs + rnf (Constructor as t) = rnf as `seq` rnf t + rnf (Type as ctx) = rnf as `seq` rnf ctx + rnf (NewType as ctx) = rnf as `seq` rnf ctx + rnf (Data as ctx) = rnf as `seq` rnf ctx + rnf (Class as ctx) = rnf as `seq` rnf ctx + rnf (TypeFam as ctx a) = rnf as `seq` rnf ctx `seq` rnf a + rnf (DataFam as ctx a) = rnf as `seq` rnf ctx `seq` rnf a + rnf (PatConstructor as t) = rnf as `seq` rnf t + rnf (PatSelector ft t c) = rnf ft `seq` rnf t `seq` rnf c + +instance ToJSON SymbolInfo where + toJSON (Function ft) = object [what "function", "type" .= ft] + toJSON (Method ft cls) = object [what "method", "type" .= ft, "class" .= cls] + toJSON (Selector ft t cs) = object [what "selector", "type" .= ft, "parent" .= t, "constructors" .= cs] + toJSON (Constructor as t) = object [what "ctor", "args" .= as, "type" .= t] + toJSON (Type as ctx) = object [what "type", "args" .= as, "ctx" .= ctx] + toJSON (NewType as ctx) = object [what "newtype", "args" .= as, "ctx" .= ctx] + toJSON (Data as ctx) = object [what "data", "args" .= as, "ctx" .= ctx] + toJSON (Class as ctx) = object [what "class", "args" .= as, "ctx" .= ctx] + toJSON (TypeFam as ctx a) = object [what "type-family", "args" .= as, "ctx" .= ctx, "associate" .= a] + toJSON (DataFam as ctx a) = object [what "data-family", "args" .= as, "ctx" .= ctx, "associate" .= a] + toJSON (PatConstructor as t) = object [what "pat-ctor", "args" .= as, "pat-type" .= t] + toJSON (PatSelector ft t c) = object [what "pat-selector", "type" .= ft, "pat-type" .= t, "constructor" .= c] + +class EmptySymbolInfo a where + infoOf :: a -> SymbolInfo + +instance EmptySymbolInfo SymbolInfo where + infoOf = id + +instance (Monoid a, EmptySymbolInfo r) => EmptySymbolInfo (a -> r) where + infoOf f = infoOf $ f mempty + +symbolType :: Symbol -> String +symbolType s = case _symbolInfo s of + Function{} -> "function" + Method{} -> "method" + Selector{} -> "selector" + Constructor{} -> "ctor" + Type{} -> "type" + NewType{} -> "newtype" + Data{} -> "data" + Class{} -> "class" + TypeFam{} -> "type-family" + DataFam{} -> "data-family" + PatConstructor{} -> "pat-ctor" + PatSelector{} -> "pat-selector" + +what :: String -> Pair +what n = "what" .= n + +instance FromJSON SymbolInfo where + parseJSON = withObject "symbol info" $ \v -> msum [ + gwhat "function" v >> (Function <$> v .::? "type"), + gwhat "method" v >> (Method <$> v .::? "type" <*> v .:: "class"), + gwhat "selector" v >> (Selector <$> v .::? "type" <*> v .:: "parent" <*> v .::?! "constructors"), + gwhat "ctor" v >> (Constructor <$> v .::?! "args" <*> v .:: "type"), + gwhat "type" v >> (Type <$> v .::?! "args" <*> v .::?! "ctx"), + gwhat "newtype" v >> (NewType <$> v .::?! "args" <*> v .::?! "ctx"), + gwhat "data" v >> (Data <$> v .::?! "args" <*> v .::?! "ctx"), + gwhat "class" v >> (Class <$> v .::?! "args" <*> v .::?! "ctx"), + gwhat "type-family" v >> (TypeFam <$> v .::?! "args" <*> v .::?! "ctx" <*> v .::? "associate"), + gwhat "data-family" v >> (DataFam <$> v .::?! "args" <*> v .::?! "ctx" <*> v .::? "associate"), + gwhat "pat-ctor" v >> (PatConstructor <$> v .::?! "args" <*> v .::? "pat-type"), + gwhat "pat-selector" v >> (PatSelector <$> v .::? "type" <*> v .::? "pat-type" <*> v .:: "constructor")] + +gwhat :: String -> Object -> Parser () +gwhat n v = do + s <- v .:: "what" + guard (s == n) + +-- | Symbol in scope with qualifier +data ScopeSymbol = ScopeSymbol { + _scopeQualifier :: Maybe Text, + _scopeSymbol :: SymbolId } + deriving (Eq, Ord) + +instance Show ScopeSymbol where + show (ScopeSymbol q s) = maybe "" (\q' -> T.unpack q' ++ ".") q ++ show s + +instance ToJSON ScopeSymbol where + toJSON (ScopeSymbol q s) = toJSON s `objectUnion` object (noNulls ["qualifier" .= q]) + +instance FromJSON ScopeSymbol where + parseJSON = withObject "scope-symbol" $ \v -> ScopeSymbol <$> + (v .::? "qualifier") <*> + parseJSON (Object v) + +-- | Symbol usage +data SymbolUsage = SymbolUsage { + _symbolUsed :: Symbol, + _symbolUsedIn :: ModuleId, + _symbolUsedPosition :: Position } + deriving (Eq, Ord) + +instance Show SymbolUsage where + show (SymbolUsage s m p) = show s ++ " at " ++ show m ++ ":" ++ show p + +instance ToJSON SymbolUsage where + toJSON (SymbolUsage s m p) = object $ noNulls ["symbol" .= s, "in" .= m, "at" .= p] + +instance FromJSON SymbolUsage where + parseJSON = withObject "symbol-usage" $ \v -> SymbolUsage <$> + v .:: "symbol" <*> + v .:: "in" <*> + v .:: "at" + +-- | Inspection data +data Inspection = + -- | No inspection + InspectionNone | + -- | Time and flags of inspection + InspectionAt { + _inspectionAt :: POSIXTime, + _inspectionOpts :: [Text] } + deriving (Eq, Ord) + +instance NFData Inspection where + rnf InspectionNone = () + rnf (InspectionAt t fs) = rnf t `seq` rnf fs + +instance Show Inspection where + show InspectionNone = "none" + show (InspectionAt tm fs) = "mtime " ++ show tm ++ ", flags [" ++ intercalate ", " (map T.unpack fs) ++ "]" + +instance Read POSIXTime where + readsPrec i = map (first (fromIntegral :: Integer -> POSIXTime)) . readsPrec i + +instance Monoid Inspection where + mempty = InspectionNone + mappend InspectionNone r = r + mappend l InspectionNone = l + mappend (InspectionAt ltm lopts) (InspectionAt rtm ropts) + | ltm >= rtm = InspectionAt ltm lopts + | otherwise = InspectionAt rtm ropts + +instance ToJSON Inspection where + toJSON InspectionNone = object ["inspected" .= False] + toJSON (InspectionAt tm fs) = object [ + "mtime" .= (fromRational (toRational tm) :: Double), + "flags" .= fs] + +instance FromJSON Inspection where + parseJSON = withObject "inspection" $ \v -> + ((const InspectionNone :: Bool -> Inspection) <$> v .:: "inspected") <|> + (InspectionAt <$> ((fromRational . (toRational :: Double -> Rational)) <$> v .:: "mtime") <*> (v .:: "flags")) + +-- | Is left @Inspection@ fresh comparing to right one +fresh :: Inspection -> Inspection -> Bool +fresh InspectionNone InspectionNone = True +fresh InspectionNone _ = False +fresh _ InspectionNone = True +fresh (InspectionAt tm _) (InspectionAt tm' _) = tm' - tm < 0.01 + +-- | Inspected entity +data Inspected k t a = Inspected { + _inspection :: Inspection, + _inspectedKey :: k, + _inspectionTags :: Set t, + _inspectionResult :: Either HsDevError a } + +inspectedTup :: Inspected k t a -> (Inspection, k, Set t, Maybe a) +inspectedTup (Inspected insp i tags res) = (insp, i, tags, either (const Nothing) Just res) + +instance (Eq k, Eq t, Eq a) => Eq (Inspected k t a) where + (==) = (==) `on` inspectedTup + +instance (Ord k, Ord t, Ord a) => Ord (Inspected k t a) where + compare = comparing inspectedTup + +instance Functor (Inspected k t) where + fmap f insp = insp { + _inspectionResult = fmap f (_inspectionResult insp) } + +instance Foldable (Inspected k t) where + foldMap f = either mempty f . _inspectionResult + +instance Traversable (Inspected k t) where + traverse f (Inspected insp i ts r) = Inspected insp i ts <$> either (pure . Left) (liftA Right . f) r + +instance (NFData k, NFData t, NFData a) => NFData (Inspected k t a) where + rnf (Inspected t i ts r) = rnf t `seq` rnf i `seq` rnf ts `seq` rnf r + +instance (ToJSON k, ToJSON t, ToJSON a) => ToJSON (Inspected k t a) where + toJSON im = object [ + "inspection" .= _inspection im, + "location" .= _inspectedKey im, + "tags" .= S.toList (_inspectionTags im), + either ("error" .=) ("result" .=) (_inspectionResult im)] + +instance (FromJSON k, Ord t, FromJSON t, FromJSON a) => FromJSON (Inspected k t a) where + parseJSON = withObject "inspected" $ \v -> Inspected <$> + v .:: "inspection" <*> + v .:: "location" <*> + (S.fromList <$> (v .::?! "tags")) <*> + ((Left <$> v .:: "error") <|> (Right <$> v .:: "result")) + +-- | Empty tags +noTags :: Set t +noTags = S.empty + +-- | One tag +tag :: t -> Set t +tag = S.singleton + +data ModuleTag = InferredTypesTag | RefinedDocsTag | OnlyHeaderTag deriving (Eq, Ord, Read, Show, Enum, Bounded) + +instance NFData ModuleTag where + rnf InferredTypesTag = () + rnf RefinedDocsTag = () + rnf OnlyHeaderTag = () + +instance ToJSON ModuleTag where + toJSON InferredTypesTag = toJSON ("types" :: String) + toJSON RefinedDocsTag = toJSON ("docs" :: String) + toJSON OnlyHeaderTag = toJSON ("header" :: String) + +instance FromJSON ModuleTag where + parseJSON = withText "module-tag" $ \txt -> msum [ + guard (txt == "types") >> return InferredTypesTag, + guard (txt == "docs") >> return RefinedDocsTag, + guard (txt == "header") >> return OnlyHeaderTag] + +-- | Inspected module +type InspectedModule = Inspected ModuleLocation ModuleTag Module + +instance Show InspectedModule where + show (Inspected i mi ts m) = unlines [either showError show m, "\tinspected: " ++ show i, "\ttags: " ++ intercalate ", " (map show $ S.toList ts)] where + showError :: HsDevError -> String + showError e = unlines $ ("\terror: " ++ show e) : case mi of + FileModule f p -> ["file: " ++ f ^. path, "project: " ++ maybe "" (view (projectPath . path)) p] + InstalledModule c p n -> ["cabal: " ++ show c, "package: " ++ show p, "name: " ++ T.unpack n] + OtherLocation src -> ["other location: " ++ T.unpack src] + NoLocation -> ["no location"] + +notInspected :: ModuleLocation -> InspectedModule +notInspected mloc = Inspected mempty mloc noTags (Left $ NotInspected mloc) + +instance Documented ModuleId where + brief m = brief $ _moduleLocation m + detailed = brief + +instance Documented SymbolId where + brief s = "{} from {}" ~~ _symbolName s ~~ brief (_symbolModule s) + detailed = brief + +instance Documented Module where + brief = brief . _moduleId + detailed m = T.unlines (brief m : info) where + info = [ + "\texports: {}" ~~ T.intercalate ", " (map brief (_moduleExports m))] + +instance Documented Symbol where + brief = brief . _symbolId + detailed s = T.unlines [brief s, info] where + info = case _symbolInfo s of + Function t -> "\t" `T.append` T.intercalate ", " (catMaybes [Just "function", fmap ("type: {}" ~~) t]) + Method t p -> "\t" `T.append` T.intercalate ", " (catMaybes [Just "method", fmap ("type: {}" ~~) t, Just $ "parent: {}" ~~ p]) + Selector t p _ -> "\t" `T.append` T.intercalate ", " (catMaybes [Just "selector", fmap ("type: {}" ~~) t, Just $ "parent: {}" ~~ p]) + Constructor args p -> "\t" `T.append` T.intercalate ", " ["constructor", "args: {}" ~~ T.unwords args, "parent: {}" ~~ p] + Type args ctx -> "\t" `T.append` T.intercalate ", " ["type", "args: {}" ~~ T.unwords args, "ctx: {}" ~~ T.unwords ctx] + NewType args ctx -> "\t" `T.append` T.intercalate ", " ["newtype", "args: {}" ~~ T.unwords args, "ctx: {}" ~~ T.unwords ctx] + Data args ctx -> "\t" `T.append` T.intercalate ", " ["data", "args: {}" ~~ T.unwords args, "ctx: {}" ~~ T.unwords ctx] + Class args ctx -> "\t" `T.append` T.intercalate ", " ["class", "args: {}" ~~ T.unwords args, "ctx: {}" ~~ T.unwords ctx] + TypeFam args ctx _ -> "\t" `T.append` T.intercalate ", " ["type family", "args: {}" ~~ T.unwords args, "ctx: {}" ~~ T.unwords ctx] + DataFam args ctx _ -> "\t" `T.append` T.intercalate ", " ["data family", "args: {}" ~~ T.unwords args, "ctx: {}" ~~ T.unwords ctx] + PatConstructor args p -> "\t" `T.append` T.intercalate ", " (catMaybes [Just "pattern constructor", Just $ "args: {}" ~~ T.unwords args, fmap ("pat-type: {}" ~~) p]) + PatSelector t p _ -> "\t" `T.append` T.intercalate ", " (catMaybes [Just "pattern selector", fmap ("type: {}" ~~) t, fmap ("pat-type: {}" ~~) p]) + +makeLenses ''Module +makeLenses ''Symbol +makeLenses ''SymbolInfo +makeLenses ''ScopeSymbol +makeLenses ''SymbolUsage +makeLenses ''Inspection +makeLenses ''Inspected + +inspected :: Traversal (Inspected k t a) (Inspected k t b) a b +inspected = inspectionResult . _Right + +nullifyInfo :: SymbolInfo -> SymbolInfo +nullifyInfo = chain [ + set functionType mempty, + set parentClass mempty, + set parentType mempty, + set selectorConstructors mempty, + set typeArgs mempty, + set typeContext mempty, + set familyAssociate mempty, + set patternType mempty, + set patternConstructor mempty] + +instance Sourced Module where + sourcedName = moduleId . moduleName + sourcedDocs = moduleDocs . _Just + sourcedModule = moduleId + +instance Sourced Symbol where + sourcedName = symbolId . symbolName + sourcedDocs = symbolDocs . _Just + sourcedModule = symbolId . symbolModule + sourcedLocation = symbolPosition . _Just
− src/HsDev/Symbols/Util.hs
@@ -1,203 +0,0 @@-module HsDev.Symbols.Util (- projectOf, packageDbOf, packageOf,- inProject, inDepsOfTarget, inDepsOfFile, inDepsOfProject, inPackageDb, inPackageDbStack, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, installed, standalone,- imports, qualifier, moduleImported, visible, inScope,- newestPackage,- sourceModule, visibleModule, preferredModule, uniqueModules,- allOf, anyOf- ) where--import Control.Arrow ((***), (&&&), first)-import Control.Lens (view)-import Control.Monad (liftM)-import Data.Function (on)-import Data.Maybe-import Data.List (maximumBy, groupBy, sortBy, partition)-import Data.Ord (comparing)-import Data.String (fromString)-import System.FilePath (normalise)--import HsDev.Symbols-import HsDev.Util (ordNub)---- | Get module project-projectOf :: ModuleId -> Maybe Project-projectOf m = case view moduleIdLocation m of- FileModule _ proj -> proj- _ -> Nothing---- | Get module cabal-packageDbOf :: ModuleId -> Maybe PackageDb-packageDbOf m = case view moduleIdLocation m of- InstalledModule c _ _ -> Just c- _ -> Nothing---- | Get module package-packageOf :: ModuleId -> Maybe ModulePackage-packageOf m = case view moduleIdLocation m of- InstalledModule _ package' _ -> package'- _ -> Nothing---- | Check if module in project-inProject :: Project -> ModuleId -> Bool-inProject p m = projectOf m == Just p---- | Check if module in deps of project target-inDepsOfTarget :: Info -> ModuleId -> Bool-inDepsOfTarget i m = any (`inPackage` m) $ view infoDepends i---- | Check if module in deps of source-inDepsOfFile :: Project -> FilePath -> ModuleId -> Bool-inDepsOfFile p f m = any (`inDepsOfTarget` m) (fileTargets p f)---- | Check if module in deps of project-inDepsOfProject :: Project -> ModuleId -> Bool-inDepsOfProject = maybe (const False) (anyPackage . ordNub . concatMap (view infoDepends) . infos) . view projectDescription where- anyPackage :: [String] -> ModuleId -> Bool- anyPackage = liftM or . mapM inPackage---- | Check if module in package-db-inPackageDb :: PackageDb -> ModuleId -> Bool-inPackageDb c m = case view moduleIdLocation m of- InstalledModule d _ _ -> d == c- _ -> False---- | Check if module in one of sandboxes-inPackageDbStack :: PackageDbStack -> ModuleId -> Bool-inPackageDbStack dbs m = case view moduleIdLocation m of- InstalledModule d _ _ -> d `elem` packageDbs dbs- _ -> False---- | Check if module in package-inPackage :: String -> ModuleId -> Bool-inPackage p m = case view moduleIdLocation m of- InstalledModule _ package' _ -> Just p == fmap (view packageName) package'- _ -> False--inVersion :: String -> ModuleId -> Bool-inVersion v m = case view moduleIdLocation m of- InstalledModule _ package' _ -> Just v == fmap (view packageVersion) package'- _ -> False---- | Check if module in file-inFile :: FilePath -> ModuleId -> Bool-inFile fpath m = case view moduleIdLocation m of- FileModule f _ -> f == normalise fpath- _ -> False---- | Check if module in source-inModuleSource :: Maybe String -> ModuleId -> Bool-inModuleSource src m = case view moduleIdLocation m of- ModuleSource src' -> src' == src- _ -> False---- | Check if declaration is in module-inModule :: String -> ModuleId -> Bool-inModule mname m = fromString mname == view moduleIdName m---- | Check if module defined in file-byFile :: ModuleId -> Bool-byFile m = case view moduleIdLocation m of- FileModule _ _ -> True- _ -> False---- | Check if module got from cabal database-installed :: ModuleId -> Bool-installed m = case view moduleIdLocation m of- InstalledModule _ _ _ -> True- _ -> False---- | Check if module is standalone-standalone :: ModuleId -> Bool-standalone m = case view moduleIdLocation m of- FileModule _ Nothing -> True- _ -> False---- | Get list of imports-imports :: Module -> [Import]-imports = view moduleImports---- | Get list of imports, which can be accessed with specified qualifier or unqualified-qualifier :: Module -> Maybe String -> [Import]-qualifier m q = filter (importQualifier (fmap fromString q)) $- import_ (fromString "Prelude") :- import_ (view moduleName m) :- imports m---- | Check if module imported via imports specified-moduleImported :: ModuleId -> [Import] -> Bool-moduleImported m = any (\i -> view moduleIdName m == view importModuleName i)---- | Check if module visible from this module within this project-visible :: Project -> ModuleId -> ModuleId -> Bool-visible p (ModuleId _ (FileModule src _)) m =- inProject p m || any (`inPackage` m) deps || maybe False ((`elem` deps) . view projectName) (projectOf m)- where- deps = concatMap (view infoDepends) $ fileTargets p src-visible _ _ _ = False---- | Check if module is in scope with qualifier-inScope :: Module -> Maybe String -> ModuleId -> Bool-inScope this q m = m `moduleImported` qualifier this q---- | Select symbols with last package version-newestPackage :: Symbol a => [a] -> [a]-newestPackage =- uncurry (++) .- ((selectNewest . groupPackages) *** map snd) .- partition (isJust . fst) .- map ((mpackage . symbolModuleLocation) &&& id)- where- mpackage (InstalledModule _ (Just p) _) = Just p- mpackage _ = Nothing- pname = fmap (view packageName) . fst- pver = fmap (view packageVersion) . fst- groupPackages :: [(Maybe ModulePackage, a)] -> [(Maybe ModulePackage, [a])]- groupPackages = map (first head . unzip) . groupBy ((==) `on` fst) . sortBy (comparing fst)- selectNewest :: [(Maybe ModulePackage, [a])] -> [a]- selectNewest =- concatMap (snd . maximumBy (comparing pver)) .- groupBy ((==) `on` pname) .- sortBy (comparing pname)---- | Select module, defined by sources-sourceModule :: Maybe Project -> [Module] -> Maybe Module-sourceModule proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (byFile . view moduleId) ms---- | Select module, visible in project or cabal--- TODO: PackageDbStack?-visibleModule :: PackageDb -> Maybe Project -> [Module] -> Maybe Module-visibleModule d proj ms = listToMaybe $ maybe (const []) (filter . (. view moduleId) . inProject) proj ms ++ filter (inPackageDb d . view moduleId) ms---- | Select preferred visible module--- TODO: PackageDbStack?-preferredModule :: PackageDb -> Maybe Project -> [ModuleId] -> Maybe ModuleId-preferredModule d proj ms = listToMaybe $ concatMap (`filter` ms) order where- order = [- maybe (const False) inProject proj,- byFile,- inPackageDb d,- const True]---- | Remove duplicate modules, leave only `preferredModule`--- TODO: PackageDbStack?-uniqueModules :: PackageDb -> Maybe Project -> [ModuleId] -> [ModuleId]-uniqueModules d proj =- mapMaybe (preferredModule d proj) .- groupBy ((==) `on` view moduleIdName) .- sortBy (comparing (view moduleIdName))---- | Select value, satisfying to all predicates-allOf :: [a -> Bool] -> a -> Bool-allOf ps x = all ($ x) ps---- | Select value, satisfying one of predicates-anyOf :: [a -> Bool] -> a -> Bool-anyOf ps x = any ($ x) ps---- | Is file info actual?---isActual :: Symbol a -> IO Bool---isActual = maybe (return False) checkStamp . symbolLocation where--- checkStamp l = do--- actualStamp <- getModificationTime (locationFile l)--- return $ Just actualStamp == locationTimeStamp l
src/HsDev/Tools/AutoFix.hs view
@@ -1,92 +1,67 @@-{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Tools.AutoFix (- Correction(..), correctionMessage, corrector,- corrections,- autoFix,- CorrectorMatch,- correctors,- match,- findCorrector,-- module Data.Text.Region,- module HsDev.Tools.Types- ) where--import Control.Applicative-import Control.Lens hiding ((.=), at)-import Data.Aeson-import Data.Maybe (listToMaybe, mapMaybe)-import Data.Text.Region hiding (Region(..))-import qualified Data.Text.Region as R--import HsDev.Symbols.Location (Position(..), Region(..))-import HsDev.Tools.Base-import HsDev.Tools.Types-import HsDev.Util ((.::))--data Correction = Correction {- _correctionMessage :: String,- _corrector :: Replace String }- deriving (Eq, Show)--instance ToJSON Correction where- toJSON (Correction msg cor) = object [- "message" .= msg,- "corrector" .= cor]--instance FromJSON Correction where- parseJSON = withObject "correction" $ \v -> Correction <$>- v .:: "message" <*>- v .:: "corrector"--makeLenses ''Correction--corrections :: [Note OutputMessage] -> [Note Correction]-corrections = mapMaybe toCorrection where- toCorrection :: Note OutputMessage -> Maybe (Note Correction)- toCorrection n = useSuggestion <|> findCorrector n where- -- Use existing suggestion- useSuggestion :: Maybe (Note Correction)- useSuggestion = do- sugg <- view (note . messageSuggestion) n- return $ set- note- (Correction- (view (note . message) n)- (replace (fromRegion $ view noteRegion n) sugg))- n---- | Apply corrections-autoFix :: [Note Correction] -> ([Note Correction], Maybe String) -> ([Note Correction], Maybe String)-autoFix ns (upd, mcts) = (over (each . note . corrector . replaceRegion) (update act) upd, over _Just (apply act) mcts) where- act = Edit (ns ^.. each . note . corrector)--type CorrectorMatch = Note OutputMessage -> Maybe (Note Correction)--correctors :: [CorrectorMatch]-correctors = [- match "^The (?:qualified )?import of .([\\w\\.]+). is redundant" $ \_ rgn -> Correction -- There are different quotes in Windows/Linux- "Redundant import"- (cut- (expandLines rgn)),- match "^(.*?)\nFound:\n (.*?)\nWhy not:\n (.*?)$" $ \g rgn -> Correction- (g `at` 1)- (replace- ((rgn ^. regionFrom) `regionSize` pt 0 (length $ g `at` 2))- (g `at` 3))]--match :: String -> ((Int -> Maybe String) -> R.Region -> Correction) -> CorrectorMatch-match pat f n = do- g <- matchRx pat (view (note . message) n)- return $ set note (f g (fromRegion $ view noteRegion n)) n--findCorrector :: Note OutputMessage -> Maybe (Note Correction)-findCorrector n = listToMaybe $ mapMaybe ($ n) correctors--fromRegion :: Region -> R.Region-fromRegion (Region f t) = fromPosition f `till` fromPosition t--fromPosition :: Position -> Point-fromPosition (Position l c) = pt (pred l) (pred c)+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Tools.AutoFix ( + corrections, + CorrectorMatch, + correctors, + match, + findCorrector, + + module Data.Text.Region, + module HsDev.Tools.Refact, + module HsDev.Tools.Types + ) where + +import Control.Applicative +import Control.Lens hiding ((.=), at) +import Data.Maybe (listToMaybe, mapMaybe) +import Data.String (fromString) +import Data.Text (Text) +import Data.Text.Lens (unpacked) +import Data.Text.Region hiding (Region(..), update) +import qualified Data.Text.Region as R + +import HsDev.Tools.Refact +import HsDev.Tools.Base +import HsDev.Tools.Types + +instance Regioned a => Regioned (Note a) where + regions = note . regions + +corrections :: [Note OutputMessage] -> [Note Refact] +corrections = mapMaybe toRefact where + toRefact :: Note OutputMessage -> Maybe (Note Refact) + toRefact n = useSuggestion <|> findCorrector n where + -- Use existing suggestion + useSuggestion :: Maybe (Note Refact) + useSuggestion = do + sugg <- view (note . messageSuggestion) n + return $ set + note + (Refact + (view (note . message) n) + (replace (fromRegion $ view noteRegion n) sugg)) + n + +type CorrectorMatch = Note OutputMessage -> Maybe (Note Refact) + +correctors :: [CorrectorMatch] +correctors = [ + match "^The (?:qualified )?import of .([\\w\\.]+). is redundant" $ \_ rgn -> Refact -- There are different quotes in Windows/Linux + "Redundant import" + (cut + (expandLines rgn)), + match "^(.*?)\nFound:\n (.*?)\nWhy not:\n (.*?)$" $ \g rgn -> Refact + (g `at` 1) + (replace + ((rgn ^. regionFrom) `regionSize` pt 0 (contentsLength $ g `at` 2)) + (g `at` 3))] + +match :: String -> ((Int -> Maybe Text) -> R.Region -> Refact) -> CorrectorMatch +match pat f n = do + g <- matchRx pat (view (note . message . unpacked) n) + return $ set note (f (fmap fromString . g) (fromRegion $ view noteRegion n)) n + +findCorrector :: Note OutputMessage -> Maybe (Note Refact) +findCorrector n = listToMaybe $ mapMaybe ($ n) correctors
src/HsDev/Tools/Base.hs view
@@ -1,117 +1,101 @@-module HsDev.Tools.Base (- runTool, runTool_,- Result, ToolM,- runWait, runWait_,- tool, tool_,- matchRx, splitRx, replaceRx,- at, at_,- inspect,- -- * Read parse utils- ReadM,- readParse, parseReads, parseRead,-- module HsDev.Tools.Types- ) where--import Control.Lens (set)-import Control.Monad.Catch (MonadCatch)-import Control.Monad.Except-import Control.Monad.State-import Data.Array (assocs)-import Data.List (unfoldr, intercalate)-import Data.Maybe (fromMaybe, listToMaybe)-import System.Exit-import System.Process-import Text.Regex.PCRE ((=~), MatchResult(..))--import HsDev.Error-import HsDev.Tools.Types-import HsDev.Symbols-import HsDev.Util (liftIOErrors)---- | Run tool, throwing HsDevError on fail-runTool :: FilePath -> [String] -> String -> IO String-runTool name args input = hsdevLiftIOWith onIOError $ do- (code, out, err) <- readProcessWithExitCode name args input- case code of- ExitFailure ecode -> hsdevError $ ToolError name $- "exited with code " ++ show ecode ++ ": " ++ err- ExitSuccess -> return out- where- onIOError s = ToolError name $ unlines [- "args: [" ++ intercalate ", " args ++ "]",- "stdin: " ++ input,- "error: " ++ s]---- | Run tool with not stdin-runTool_ :: FilePath -> [String] -> IO String-runTool_ name args = runTool name args ""--type Result = Either String String-type ToolM a = ExceptT String IO a---- | Run command and wait for result-runWait :: FilePath -> [String] -> String -> IO Result-runWait name args input = do- (code, out, err) <- readProcessWithExitCode name args input- return $ if code == ExitSuccess && not (null out) then Right out else Left err---- | Run command with no input-runWait_ :: FilePath -> [String] -> IO Result-runWait_ name args = runWait name args ""---- | Tool-tool :: FilePath -> [String] -> String -> ToolM String-tool name args input = liftIOErrors $ ExceptT $ runWait name args input---- | Tool with no input-tool_ :: FilePath -> [String] -> ToolM String-tool_ name args = tool name args ""--matchRx :: String -> String -> Maybe (Int -> Maybe String)-matchRx pat str = if matched then Just look else Nothing where- m :: MatchResult String- m = str =~ pat- matched = not $ null $ mrMatch m- groups = filter (not . null . snd) $ assocs $ mrSubs m- look i = lookup i groups--splitRx :: String -> String -> [String]-splitRx pat = unfoldr split' . Just where- split' :: Maybe String -> Maybe (String, Maybe String)- split' Nothing = Nothing- split' (Just str) = case str =~ pat of- (pre, "", "") -> Just (pre, Nothing)- (pre, _, post) -> Just (pre, Just post)--replaceRx :: String -> String -> String -> String-replaceRx pat w = intercalate w . splitRx pat--at :: (Int -> Maybe String) -> Int -> String-at g i = fromMaybe (error $ "Can't find group " ++ show i) $ g i--at_ :: (Int -> Maybe String) -> Int -> String-at_ g = fromMaybe "" . g--inspect :: MonadCatch m => ModuleLocation -> m Inspection -> m Module -> m InspectedModule-inspect mloc insp act = execStateT inspect' (notInspected mloc) where- inspect' = do- r <- hsdevCatch $ hsdevLiftIO $ do- i <- lift insp- modify (set inspection i)- lift act- modify (set inspectionResult r)--type ReadM a = StateT String [] a---- | Parse readable value-readParse :: Read a => ReadM a-readParse = StateT reads---- | Run parser-parseReads :: String -> ReadM a -> [a]-parseReads = flip evalStateT---- | Run parser and select first result-parseRead :: String -> ReadM a -> Maybe a-parseRead s = listToMaybe . parseReads s+module HsDev.Tools.Base ( + runTool, runTool_, + Result, ToolM, + runWait, runWait_, + tool, tool_, + matchRx, splitRx, replaceRx, + at, at_, + inspect, + + module HsDev.Tools.Types + ) where + +import Control.Lens (set) +import Control.Monad.Catch (MonadCatch) +import Control.Monad.Except +import Control.Monad.State +import Data.Array (assocs) +import Data.List (unfoldr, intercalate) +import Data.Maybe (fromMaybe) +import Data.String +import System.Exit +import System.Process +import Text.Regex.PCRE ((=~), MatchResult(..)) + +import HsDev.Error +import HsDev.Tools.Types +import HsDev.Symbols +import HsDev.Util (liftIOErrors) + +-- | Run tool, throwing HsDevError on fail +runTool :: FilePath -> [String] -> String -> IO String +runTool name args input = hsdevLiftIOWith onIOError $ do + (code, out, err) <- readProcessWithExitCode name args input + case code of + ExitFailure ecode -> hsdevError $ ToolError name $ + "exited with code " ++ show ecode ++ ": " ++ err + ExitSuccess -> return out + where + onIOError s = ToolError name $ unlines [ + "args: [" ++ intercalate ", " args ++ "]", + "stdin: " ++ input, + "error: " ++ s] + +-- | Run tool with not stdin +runTool_ :: FilePath -> [String] -> IO String +runTool_ name args = runTool name args "" + +type Result = Either String String +type ToolM a = ExceptT String IO a + +-- | Run command and wait for result +runWait :: FilePath -> [String] -> String -> IO Result +runWait name args input = do + (code, out, err) <- readProcessWithExitCode name args input + return $ if code == ExitSuccess && not (null out) then Right out else Left err + +-- | Run command with no input +runWait_ :: FilePath -> [String] -> IO Result +runWait_ name args = runWait name args "" + +-- | Tool +tool :: FilePath -> [String] -> String -> ToolM String +tool name args input = liftIOErrors $ ExceptT $ runWait name args input + +-- | Tool with no input +tool_ :: FilePath -> [String] -> ToolM String +tool_ name args = tool name args "" + +matchRx :: String -> String -> Maybe (Int -> Maybe String) +matchRx pat str = if matched then Just look else Nothing where + m :: MatchResult String + m = str =~ pat + matched = not $ null $ mrMatch m + groups = filter (not . null . snd) $ assocs $ mrSubs m + look i = lookup i groups + +splitRx :: String -> String -> [String] +splitRx pat = unfoldr split' . Just where + split' :: Maybe String -> Maybe (String, Maybe String) + split' Nothing = Nothing + split' (Just str) = case str =~ pat of + (pre, "", "") -> Just (pre, Nothing) + (pre, _, post) -> Just (pre, Just post) + +replaceRx :: String -> String -> String -> String +replaceRx pat w = intercalate w . splitRx pat + +at :: (Int -> Maybe a) -> Int -> a +at g i = fromMaybe (error $ "Can't find group " ++ show i) $ g i + +at_ :: IsString s => (Int -> Maybe s) -> Int -> s +at_ g = fromMaybe (fromString "") . g + +inspect :: MonadCatch m => ModuleLocation -> m Inspection -> m Module -> m InspectedModule +inspect mloc insp act = execStateT inspect' (notInspected mloc) where + inspect' = do + r <- hsdevCatch $ hsdevLiftIO $ do + i <- lift insp + modify (set inspection i) + lift act + modify (set inspectionResult r)
src/HsDev/Tools/Cabal.hs view
@@ -1,85 +1,85 @@-{-# LANGUAGE OverloadedStrings, CPP #-}--module HsDev.Tools.Cabal (- CabalPackage(..),- cabalList,-- -- * Reexports- Version, License(..)- ) where--import Control.Arrow-import Control.Monad-import Data.Aeson-import Data.Char (isSpace)-import Data.Maybe-import Distribution.License-import Distribution.Text-import Distribution.Version--import HsDev.Tools.Base-import HsDev.Util--data CabalPackage = CabalPackage {- cabalPackageName :: String,- cabalPackageSynopsis :: Maybe String,- cabalPackageDefaultVersion :: Maybe Version,- cabalPackageInstalledVersions :: [Version],- cabalPackageHomepage :: Maybe String,- cabalPackageLicense :: Maybe License }- deriving (Eq, Read, Show)--instance ToJSON CabalPackage where- toJSON cp = object [- "name" .= cabalPackageName cp,- "synopsis" .= cabalPackageSynopsis cp,- "default-version" .= fmap display (cabalPackageDefaultVersion cp),- "installed-versions" .= map display (cabalPackageInstalledVersions cp),- "homepage" .= cabalPackageHomepage cp,- "license" .= fmap display (cabalPackageLicense cp)]--instance FromJSON CabalPackage where- parseJSON = withObject "cabal-package" $ \v -> CabalPackage <$>- (v .:: "name") <*>- (v .:: "synopsis") <*>- ((join . fmap simpleParse) <$> (v .:: "default-version")) <*>- (mapMaybe simpleParse <$> (v .:: "installed-versions")) <*>- (v .:: "homepage") <*>- ((join . fmap simpleParse) <$> (v .:: "license"))--cabalList :: [String] -> ToolM [CabalPackage]-cabalList queries = do-#if mingw32_HOST_OS- rs <- liftM (split (all isSpace) . lines) $ tool_ "powershell" [- "-Command",- unwords (["&", "{", "chcp 65001 | out-null;", "cabal list"] ++ queries ++ ["}"])]-#else- rs <- liftM (split (all isSpace) . lines) $ tool_ "cabal" ("list" : queries)-#endif- return $ map toPackage $ mapMaybe parseFields rs- where- toPackage :: (String, [(String, String)]) -> CabalPackage- toPackage (name, fs) = CabalPackage {- cabalPackageName = name,- cabalPackageSynopsis = lookup "Synopsis" fs,- cabalPackageDefaultVersion = (lookup "Default available version" fs >>= simpleParse),- cabalPackageInstalledVersions = fromMaybe [] (lookup "Installed versions" fs >>= mapM (simpleParse . trim) . split (== ',')),- cabalPackageHomepage = lookup "Homepage" fs,- cabalPackageLicense = lookup "License" fs >>= simpleParse }-- parseFields :: [String] -> Maybe (String, [(String, String)])- parseFields [] = Nothing- parseFields (('*':name):fs) = Just (trim name, mapMaybe parseField' fs) where- parseField' :: String -> Maybe (String, String)- parseField' str = case parseField str of- (fname, Just fval) -> Just (fname, fval)- _ -> Nothing- parseFields _ = Nothing-- -- foo: bar → (foo, bar)- parseField :: String -> (String, Maybe String)- parseField = (trim *** (parseValue . trim . drop 1)) . break (== ':')- -- [ ... ] → Nothing, ... → Just ...- parseValue :: String -> Maybe String- parseValue ('[':_) = Nothing- parseValue v = Just v+{-# LANGUAGE OverloadedStrings, CPP #-} + +module HsDev.Tools.Cabal ( + CabalPackage(..), + cabalList, + + -- * Reexports + Version, License(..) + ) where + +import Control.Arrow +import Control.Monad +import Data.Aeson +import Data.Char (isSpace) +import Data.Maybe +import Distribution.License +import Distribution.Text +import Distribution.Version + +import HsDev.Tools.Base +import HsDev.Util + +data CabalPackage = CabalPackage { + cabalPackageName :: String, + cabalPackageSynopsis :: Maybe String, + cabalPackageDefaultVersion :: Maybe Version, + cabalPackageInstalledVersions :: [Version], + cabalPackageHomepage :: Maybe String, + cabalPackageLicense :: Maybe License } + deriving (Eq, Read, Show) + +instance ToJSON CabalPackage where + toJSON cp = object [ + "name" .= cabalPackageName cp, + "synopsis" .= cabalPackageSynopsis cp, + "default-version" .= fmap display (cabalPackageDefaultVersion cp), + "installed-versions" .= map display (cabalPackageInstalledVersions cp), + "homepage" .= cabalPackageHomepage cp, + "license" .= fmap display (cabalPackageLicense cp)] + +instance FromJSON CabalPackage where + parseJSON = withObject "cabal-package" $ \v -> CabalPackage <$> + (v .:: "name") <*> + (v .:: "synopsis") <*> + ((join . fmap simpleParse) <$> (v .:: "default-version")) <*> + (mapMaybe simpleParse <$> (v .:: "installed-versions")) <*> + (v .:: "homepage") <*> + ((join . fmap simpleParse) <$> (v .:: "license")) + +cabalList :: [String] -> ToolM [CabalPackage] +cabalList queries = do +#if mingw32_HOST_OS + rs <- liftM (split (all isSpace) . lines) $ tool_ "powershell" [ + "-Command", + unwords (["&", "{", "chcp 65001 | out-null;", "cabal list"] ++ queries ++ ["}"])] +#else + rs <- liftM (split (all isSpace) . lines) $ tool_ "cabal" ("list" : queries) +#endif + return $ map toPackage $ mapMaybe parseFields rs + where + toPackage :: (String, [(String, String)]) -> CabalPackage + toPackage (name, fs) = CabalPackage { + cabalPackageName = name, + cabalPackageSynopsis = lookup "Synopsis" fs, + cabalPackageDefaultVersion = (lookup "Default available version" fs >>= simpleParse), + cabalPackageInstalledVersions = fromMaybe [] (lookup "Installed versions" fs >>= mapM (simpleParse . trim) . split (== ',')), + cabalPackageHomepage = lookup "Homepage" fs, + cabalPackageLicense = lookup "License" fs >>= simpleParse } + + parseFields :: [String] -> Maybe (String, [(String, String)]) + parseFields [] = Nothing + parseFields (('*':name):fs) = Just (trim name, mapMaybe parseField' fs) where + parseField' :: String -> Maybe (String, String) + parseField' str = case parseField str of + (fname, Just fval) -> Just (fname, fval) + _ -> Nothing + parseFields _ = Nothing + + -- foo: bar → (foo, bar) + parseField :: String -> (String, Maybe String) + parseField = (trim *** (parseValue . trim . drop 1)) . break (== ':') + -- [ ... ] → Nothing, ... → Just ... + parseValue :: String -> Maybe String + parseValue ('[':_) = Nothing + parseValue v = Just v
src/HsDev/Tools/ClearImports.hs view
@@ -1,112 +1,113 @@-module HsDev.Tools.ClearImports (- dumpMinimalImports, waitImports, cleanTmpImports,- findMinimalImports,- groupImports, splitImport,- clearImports,-- module Control.Monad.Except- ) where--import Control.Arrow-import Control.Concurrent (threadDelay)-import Control.Exception-import Control.Monad.Except-import Data.Char-import Data.List-import Data.Maybe (mapMaybe)-import System.Directory-import System.FilePath-import qualified Language.Haskell.Exts as Exts--import GHC-import GHC.Paths (libdir)--import HsDev.Util-import HsDev.Tools.Ghc.Compat---- | Dump minimal imports-dumpMinimalImports :: [String] -> FilePath -> ExceptT String IO String-dumpMinimalImports opts f = do- cur <- liftE getCurrentDirectory- file <- liftE $ canonicalizePath f- cts <- liftE $ readFileUtf8 file-- mname <- case Exts.parseFileContentsWithMode (pmode file) cts of- Exts.ParseFailed loc err -> throwError $- "Failed to parse file at " ++- Exts.prettyPrint loc ++ ":" ++ err- Exts.ParseOk (Exts.Module _ (Just (Exts.ModuleHead _ (Exts.ModuleName _ mname) _ _)) _ _ _) -> return mname- _ -> throwError "Error"-- void $ liftE $ runGhc (Just libdir) $ do- df <- getSessionDynFlags- let- df' = df {- ghcLink = NoLink,- hscTarget = HscNothing,- dumpDir = Just cur,- stubDir = Just cur,- objectDir = Just cur,- hiDir = Just cur }- (df'', _, _) <- parseDynamicFlags df' (map noLoc ("-ddump-minimal-imports" : opts))- _ <- setSessionDynFlags df''- cleanupHandler df'' $ do- t <- guessTarget file Nothing- setTargets [t]- load LoadAllTargets-- length mname `seq` return mname- where- pmode :: FilePath -> Exts.ParseMode- pmode f' = Exts.defaultParseMode {- Exts.parseFilename = f',- Exts.baseLanguage = Exts.Haskell2010,- Exts.extensions = Exts.glasgowExts ++ map Exts.parseExtension exts,- Exts.fixities = Just Exts.baseFixities }- exts = mapMaybe (stripPrefix "-X") opts---- | Read imports from file-waitImports :: FilePath -> IO [String]-waitImports f = retry 1000 $ do- is <- liftM lines $ readFile f- length is `seq` return is---- | Clean temporary files-cleanTmpImports :: FilePath -> IO ()-cleanTmpImports dir = do- dumps <- liftM (filter ((== ".imports") . takeExtension)) $ directoryContents dir- forM_ dumps $ handle ignoreIO' . retry 1000 . removeFile- where- ignoreIO' :: IOException -> IO ()- ignoreIO' _ = return ()---- | Dump and read imports-findMinimalImports :: [String] -> FilePath -> ExceptT String IO [String]-findMinimalImports opts f = do- file <- liftE $ canonicalizePath f- mname <- dumpMinimalImports opts file- is <- liftE $ waitImports (mname <.> "imports")- tmp <- liftE getCurrentDirectory- liftE $ cleanTmpImports tmp- return is---- | Groups several lines related to one import by indents-groupImports :: [String] -> [[String]]-groupImports = unfoldr getPack where- getPack [] = Nothing- getPack (s:ss) = Just $ first (s:) $ break (null . takeWhile isSpace) ss---- | Split import to import and import-list-splitImport :: [String] -> (String, String)-splitImport = splitBraces . unwords . map trim where- cut = twice $ reverse . drop 1- twice f = f . f- splitBraces = (trim *** (trim . cut)) . break (== '(')---- | Returns minimal imports for file specified-clearImports :: [String] -> FilePath -> ExceptT String IO [(String, String)]-clearImports opts = liftM (map splitImport . groupImports) . findMinimalImports opts---- | Retry action on fail-retry :: (MonadPlus m, MonadIO m) => Int -> m a -> m a-retry dt act = msum $ act : repeat ((liftIO (threadDelay dt) >>) act)+module HsDev.Tools.ClearImports ( + dumpMinimalImports, waitImports, cleanTmpImports, + findMinimalImports, + groupImports, splitImport, + clearImports, + + module Control.Monad.Except + ) where + +import Control.Arrow +import Control.Concurrent (threadDelay) +import Control.Exception +import Control.Monad.Except +import Data.Char +import Data.List +import Data.Maybe (mapMaybe) +import Data.Text (unpack) +import System.Directory +import System.FilePath +import qualified Language.Haskell.Exts as Exts + +import GHC +import GHC.Paths (libdir) + +import HsDev.Util +import HsDev.Tools.Ghc.Compat + +-- | Dump minimal imports +dumpMinimalImports :: [String] -> FilePath -> ExceptT String IO String +dumpMinimalImports opts f = do + cur <- liftE getCurrentDirectory + file <- liftE $ canonicalizePath f + cts <- liftE $ fmap unpack $ readFileUtf8 file + + mname <- case Exts.parseFileContentsWithMode (pmode file) cts of + Exts.ParseFailed loc err -> throwError $ + "Failed to parse file at " ++ + Exts.prettyPrint loc ++ ":" ++ err + Exts.ParseOk (Exts.Module _ (Just (Exts.ModuleHead _ (Exts.ModuleName _ mname) _ _)) _ _ _) -> return mname + _ -> throwError "Error" + + void $ liftE $ runGhc (Just libdir) $ do + df <- getSessionDynFlags + let + df' = df { + ghcLink = NoLink, + hscTarget = HscNothing, + dumpDir = Just cur, + stubDir = Just cur, + objectDir = Just cur, + hiDir = Just cur } + (df'', _, _) <- parseDynamicFlags df' (map noLoc ("-ddump-minimal-imports" : opts)) + _ <- setSessionDynFlags df'' + cleanupHandler df'' $ do + t <- guessTarget file Nothing + setTargets [t] + load LoadAllTargets + + length mname `seq` return mname + where + pmode :: FilePath -> Exts.ParseMode + pmode f' = Exts.defaultParseMode { + Exts.parseFilename = f', + Exts.baseLanguage = Exts.Haskell2010, + Exts.extensions = Exts.glasgowExts ++ map Exts.parseExtension exts, + Exts.fixities = Just Exts.baseFixities } + exts = mapMaybe (stripPrefix "-X") opts + +-- | Read imports from file +waitImports :: FilePath -> IO [String] +waitImports f = retry 1000 $ do + is <- liftM lines $ readFile f + length is `seq` return is + +-- | Clean temporary files +cleanTmpImports :: FilePath -> IO () +cleanTmpImports dir = do + dumps <- liftM (filter ((== ".imports") . takeExtension)) $ directoryContents dir + forM_ dumps $ handle ignoreIO' . retry 1000 . removeFile + where + ignoreIO' :: IOException -> IO () + ignoreIO' _ = return () + +-- | Dump and read imports +findMinimalImports :: [String] -> FilePath -> ExceptT String IO [String] +findMinimalImports opts f = do + file <- liftE $ canonicalizePath f + mname <- dumpMinimalImports opts file + is <- liftE $ waitImports (mname <.> "imports") + tmp <- liftE getCurrentDirectory + liftE $ cleanTmpImports tmp + return is + +-- | Groups several lines related to one import by indents +groupImports :: [String] -> [[String]] +groupImports = unfoldr getPack where + getPack [] = Nothing + getPack (s:ss) = Just $ first (s:) $ break (null . takeWhile isSpace) ss + +-- | Split import to import and import-list +splitImport :: [String] -> (String, String) +splitImport = splitBraces . unwords . map trim where + cut = twice $ reverse . drop 1 + twice f = f . f + splitBraces = (trim *** (trim . cut)) . break (== '(') + +-- | Returns minimal imports for file specified +clearImports :: [String] -> FilePath -> ExceptT String IO [(String, String)] +clearImports opts = liftM (map splitImport . groupImports) . findMinimalImports opts + +-- | Retry action on fail +retry :: (MonadPlus m, MonadIO m) => Int -> m a -> m a +retry dt act = msum $ act : repeat ((liftIO (threadDelay dt) >>) act)
src/HsDev/Tools/Ghc/Check.hs view
@@ -1,88 +1,65 @@-{-# LANGUAGE OverloadedStrings #-}--module HsDev.Tools.Ghc.Check (- checkFiles, check, checkFile, checkSource,-- Ghc,- module HsDev.Tools.Types,- module HsDev.Symbols.Types,- PackageDb(..), PackageDbStack(..), Project(..),-- recalcNotesTabs,-- module Control.Monad.Except- ) where--import Control.Lens (preview, view, each, _Just, (^..))-import Control.Monad.Except-import Data.Maybe (fromMaybe)-import System.FilePath (makeRelative)-import System.Directory (doesDirectoryExist)-import System.Log.Simple (MonadLog(..), scope)--import GHC hiding (Warning, Module, moduleName)--import Control.Concurrent.FiniteChan-import HsDev.Error-import HsDev.PackageDb-import HsDev.Symbols.Location-import HsDev.Symbols.Types-import HsDev.Project (fileTarget)-import HsDev.Tools.Base-import HsDev.Tools.Ghc.Worker-import HsDev.Tools.Ghc.Compat as C-import HsDev.Tools.Types-import HsDev.Util (readFileUtf8, ordNub)---- | Check files and collect warnings and errors-checkFiles :: (MonadLog m, GhcMonad m) => [String] -> [FilePath] -> Maybe Project -> m [Note OutputMessage]-checkFiles opts files _ = scope "check-files" $ do- ch <- liftIO newChan- withFlags $ do- modifyFlags $ C.setLogAction $ logToChan ch- addCmdOpts opts- clearTargets- mapM (`makeTarget` Nothing) files >>= loadTargets- notes <- liftIO $ stopChan ch- liftIO $ recalcNotesTabs notes---- | Check module source-check :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note OutputMessage]-check opts m msrc = scope "check" $ case view moduleLocation m of- FileModule file proj -> do- ch <- liftIO newChan- let- dir = fromMaybe- (sourceModuleRoot (view moduleName m) file) $- preview (_Just . projectPath) proj- dirExist <- liftIO $ doesDirectoryExist dir- withFlags $ (if dirExist then withCurrentDirectory dir else id) $ do- addCmdOpts opts- modifyFlags $ C.setLogAction $ logToChan ch- clearTargets- target <- makeTarget (makeRelative dir file) msrc- loadTargets [target]- notes <- liftIO $ stopChan ch- liftIO $ recalcNotesTabs notes- _ -> scope "check" $ hsdevError $ ModuleNotSource (view moduleLocation m)---- | Check module and collect warnings and errors-checkFile :: (MonadLog m, GhcMonad m) => [String] -> Module -> m [Note OutputMessage]-checkFile opts m = check opts m Nothing---- | Check module and collect warnings and errors-checkSource :: (MonadLog m, GhcMonad m) => [String] -> Module -> String -> m [Note OutputMessage]-checkSource opts m src = check opts m (Just src)---- Recalc tabs for notes-recalcNotesTabs :: [Note OutputMessage] -> IO [Note OutputMessage]-recalcNotesTabs notes = do- cts <- mapM readFileUtf8 files- let- recalc' n = fromMaybe n $ do- fname <- preview (noteSource . moduleFile) n- cts' <- lookup fname (zip files cts)- return $ recalcTabs cts' 8 n- return $ map recalc' notes- where- files = ordNub $ notes ^.. each . noteSource . moduleFile+{-# LANGUAGE OverloadedStrings #-} + +module HsDev.Tools.Ghc.Check ( + check, + + Ghc, + module HsDev.Tools.Types, + module HsDev.Symbols.Types, + PackageDb(..), PackageDbStack(..), Project(..), + + recalcNotesTabs, + + module Control.Monad.Except + ) where + +import Control.Lens (preview, view, each, (^..), (^.)) +import Control.Monad.Except +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import System.Log.Simple (MonadLog(..), scope, sendLog, Level(Trace)) + +import GHC hiding (Warning, Module, moduleName) + +import Control.Concurrent.FiniteChan +import HsDev.Error +import HsDev.PackageDb +import HsDev.Symbols.Location +import HsDev.Symbols.Types +import HsDev.Tools.Base +import HsDev.Tools.Ghc.Worker +import HsDev.Tools.Ghc.Compat as C +import HsDev.Tools.Types +import HsDev.Util (readFileUtf8, ordNub) +import System.Directory.Paths + +-- | Check module source +check :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m [Note OutputMessage] +check m msrc = scope "check" $ case view (moduleId . moduleLocation) m of + FileModule file _ -> do + ch <- liftIO newChan + let + dir = sourceRoot_ (m ^. moduleId) + ex <- liftIO $ dirExists dir + sendLog Trace "loading targets" + withFlags $ (if ex then withCurrentDirectory (dir ^. path) else id) $ do + modifyFlags $ C.setLogAction $ logToChan ch + target <- makeTarget (relPathTo dir file) msrc + loadTargets [target] + notes <- liftIO $ stopChan ch + sendLog Trace "targets checked" + liftIO $ recalcNotesTabs notes + _ -> scope "check" $ hsdevError $ ModuleNotSource (view (moduleId . moduleLocation) m) + +-- Recalc tabs for notes +recalcNotesTabs :: [Note OutputMessage] -> IO [Note OutputMessage] +recalcNotesTabs notes = do + cts <- mapM (readFileUtf8 . view path) files + let + recalc' n = fromMaybe n $ do + fname <- preview (noteSource . moduleFile) n + cts' <- lookup fname (zip files cts) + return $ recalcTabs cts' 8 n + return $ map recalc' notes + where + files = ordNub $ notes ^.. each . noteSource . moduleFile
src/HsDev/Tools/Ghc/Compat.hs view
@@ -1,154 +1,192 @@-{-# LANGUAGE CPP #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Tools.Ghc.Compat (- pkgDatabase,- UnitId, InstalledUnitId, toInstalledUnitId,- unitId, moduleUnitId, depends, getPackageDetails, patSynType, cleanupHandler, renderStyle,- LogAction, setLogAction,- languages, flags,- unqualStyle,- exposedModuleName- ) where--import qualified DynFlags as GHC-import qualified ErrUtils-import qualified GHC-import qualified Module-import qualified Packages as GHC-import qualified PatSyn as GHC-import qualified Pretty-import Outputable--#if __GLASGOW_HASKELL__ == 710-import Exception (ExceptionMonad)-import Control.Monad.Reader-#endif--#if __GLASGOW_HASKELL__ <= 800-import qualified GHC.PackageDb as GHC-#endif--pkgDatabase :: GHC.DynFlags -> Maybe [GHC.PackageConfig]-#if __GLASGOW_HASKELL__ >= 800-pkgDatabase = fmap (concatMap snd) . GHC.pkgDatabase-#elif __GLASGOW_HASKELL__ == 710-pkgDatabase = GHC.pkgDatabase-#endif--#if __GLASGOW_HASKELL__ >= 800-type UnitId = Module.UnitId-#elif __GLASGOW_HASKELL__ == 710-type UnitId = Module.PackageKey-#endif--#if __GLASGOW_HASKELL__ == 802-type InstalledUnitId = Module.InstalledUnitId-#else-type InstalledUnitId = UnitId-#endif--toInstalledUnitId :: UnitId -> InstalledUnitId-#if __GLASGOW_HASKELL__ == 802-toInstalledUnitId = Module.toInstalledUnitId-#else-toInstalledUnitId = id-#endif--unitId :: GHC.PackageConfig -> InstalledUnitId-#if __GLASGOW_HASKELL__ >= 800-unitId = GHC.unitId-#elif __GLASGOW_HASKELL__ == 710-unitId = GHC.packageKey-#endif--moduleUnitId :: GHC.Module -> UnitId-#if __GLASGOW_HASKELL__ >= 800-moduleUnitId = GHC.moduleUnitId-#elif __GLASGOW_HASKELL__ == 710-moduleUnitId = GHC.modulePackageKey-#endif--depends :: GHC.DynFlags -> GHC.PackageConfig -> [InstalledUnitId]-#if __GLASGOW_HASKELL__ >= 800-depends _ = GHC.depends-#elif __GLASGOW_HASKELL__ == 710-depends df = map (GHC.resolveInstalledPackageId df) . GHC.depends-#endif--getPackageDetails :: GHC.DynFlags -> InstalledUnitId -> GHC.PackageConfig-#if __GLASGOW_HASKELL__ == 802-getPackageDetails = GHC.getInstalledPackageDetails-#else-getPackageDetails = GHC.getPackageDetails-#endif--patSynType :: GHC.PatSyn -> GHC.Type-patSynType p = GHC.patSynInstResTy p (GHC.patSynArgs p)--#if __GLASGOW_HASKELL__ >= 800-cleanupHandler :: GHC.DynFlags -> m a -> m a-cleanupHandler _ = id-#elif __GLASGOW_HASKELL__ == 710-cleanupHandler :: (ExceptionMonad m) => GHC.DynFlags -> m a -> m a-cleanupHandler = GHC.defaultCleanupHandler-#endif--renderStyle :: Pretty.Mode -> Int -> Pretty.Doc -> String-#if __GLASGOW_HASKELL__ >= 800-renderStyle m cols = Pretty.renderStyle (Pretty.Style m cols 1.5)-#elif __GLASGOW_HASKELL__ == 710-renderStyle = Pretty.showDoc-#endif--type LogAction = GHC.DynFlags -> GHC.Severity -> GHC.SrcSpan -> ErrUtils.MsgDoc -> IO ()--setLogAction :: LogAction -> GHC.DynFlags -> GHC.DynFlags-setLogAction act fs = fs { GHC.log_action = act' } where- act' :: GHC.LogAction-#if __GLASGOW_HASKELL__ >= 800- act' df _ sev src _ msg = act df sev src msg-#elif __GLASGOW_HASKELL__ == 710- act' df sev src _ msg = act df sev src msg-#endif--#if __GLASGOW_HASKELL__ == 710-instance (Monad m, GHC.HasDynFlags m) => GHC.HasDynFlags (ReaderT r m) where- getDynFlags = lift GHC.getDynFlags-#endif--flags :: [String]-#if __GLASGOW_HASKELL__ >= 800-flags = concat [- [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags],- ["warn-" ++ option | (GHC.FlagSpec option _ _ _) <- GHC.wWarningFlags],- [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]]-#elif __GLASGOW_HASKELL__ >= 710-flags = concat [- [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags],- [option | (GHC.FlagSpec option _ _ _) <- GHC.fWarningFlags],- [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]]-#elif __GLASGOW_HASKELL__ >= 704-flags = concat [- [option | (option, _, _) <- GHC.fFlags],- [option | (option, _, _) <- GHC.fWarningFlags],- [option | (option, _, _) <- GHC.fLangFlags]]-#endif--languages :: [String]-languages = GHC.supportedLanguagesAndExtensions--unqualStyle :: GHC.DynFlags -> PprStyle-#if __GLASGOW_HASKELL__ == 802-unqualStyle df = mkUserStyle df neverQualify AllTheWay-#else-unqualStyle _ = mkUserStyle neverQualify AllTheWay-#endif--#if __GLASGOW_HASKELL__ == 802-exposedModuleName :: (a, Maybe b) -> a-exposedModuleName = fst-#else-exposedModuleName :: GHC.ExposedModule unit mname -> mname-exposedModuleName = GHC.exposedName-#endif+{-# LANGUAGE CPP #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Tools.Ghc.Compat ( + pkgDatabase, + UnitId, InstalledUnitId, toInstalledUnitId, + unitId, moduleUnitId, depends, getPackageDetails, patSynType, cleanupHandler, renderStyle, + LogAction, setLogAction, + languages, flags, + recSelParent, recSelCtors, + getFixity, + unqualStyle, + exposedModuleName + ) where + +import qualified BasicTypes +import qualified DynFlags as GHC +import qualified ErrUtils +import qualified IdInfo +import qualified GHC +import qualified Module +import qualified Name +import qualified Packages as GHC +import qualified PatSyn as GHC +import qualified Pretty +import Outputable + +#if __GLASGOW_HASKELL__ >= 800 +import Data.List (nub) +#endif + +#if __GLASGOW_HASKELL__ == 800 +import qualified IdInfo +#endif + +#if __GLASGOW_HASKELL__ == 710 +import Exception (ExceptionMonad) +import Control.Monad.Reader +#endif + +#if __GLASGOW_HASKELL__ <= 800 +import qualified GHC.PackageDb as GHC +#endif + +pkgDatabase :: GHC.DynFlags -> Maybe [GHC.PackageConfig] +#if __GLASGOW_HASKELL__ >= 800 +pkgDatabase = fmap (nub . concatMap snd) . GHC.pkgDatabase +#elif __GLASGOW_HASKELL__ == 710 +pkgDatabase = GHC.pkgDatabase +#endif + +#if __GLASGOW_HASKELL__ >= 800 +type UnitId = Module.UnitId +#elif __GLASGOW_HASKELL__ == 710 +type UnitId = Module.PackageKey +#endif + +#if __GLASGOW_HASKELL__ == 802 +type InstalledUnitId = Module.InstalledUnitId +#else +type InstalledUnitId = UnitId +#endif + +toInstalledUnitId :: UnitId -> InstalledUnitId +#if __GLASGOW_HASKELL__ == 802 +toInstalledUnitId = Module.toInstalledUnitId +#else +toInstalledUnitId = id +#endif + +unitId :: GHC.PackageConfig -> InstalledUnitId +#if __GLASGOW_HASKELL__ >= 800 +unitId = GHC.unitId +#elif __GLASGOW_HASKELL__ == 710 +unitId = GHC.packageKey +#endif + +moduleUnitId :: GHC.Module -> UnitId +#if __GLASGOW_HASKELL__ >= 800 +moduleUnitId = GHC.moduleUnitId +#elif __GLASGOW_HASKELL__ == 710 +moduleUnitId = GHC.modulePackageKey +#endif + +depends :: GHC.DynFlags -> GHC.PackageConfig -> [InstalledUnitId] +#if __GLASGOW_HASKELL__ >= 800 +depends _ = GHC.depends +#elif __GLASGOW_HASKELL__ == 710 +depends df = map (GHC.resolveInstalledPackageId df) . GHC.depends +#endif + +getPackageDetails :: GHC.DynFlags -> InstalledUnitId -> GHC.PackageConfig +#if __GLASGOW_HASKELL__ == 802 +getPackageDetails = GHC.getInstalledPackageDetails +#else +getPackageDetails = GHC.getPackageDetails +#endif + +patSynType :: GHC.PatSyn -> GHC.Type +patSynType p = GHC.patSynInstResTy p (GHC.patSynArgs p) + +#if __GLASGOW_HASKELL__ >= 800 +cleanupHandler :: GHC.DynFlags -> m a -> m a +cleanupHandler _ = id +#elif __GLASGOW_HASKELL__ == 710 +cleanupHandler :: (ExceptionMonad m) => GHC.DynFlags -> m a -> m a +cleanupHandler = GHC.defaultCleanupHandler +#endif + +renderStyle :: Pretty.Mode -> Int -> Pretty.Doc -> String +#if __GLASGOW_HASKELL__ >= 800 +renderStyle m cols = Pretty.renderStyle (Pretty.Style m cols 1.5) +#elif __GLASGOW_HASKELL__ == 710 +renderStyle = Pretty.showDoc +#endif + +type LogAction = GHC.DynFlags -> GHC.Severity -> GHC.SrcSpan -> ErrUtils.MsgDoc -> IO () + +setLogAction :: LogAction -> GHC.DynFlags -> GHC.DynFlags +setLogAction act fs = fs { GHC.log_action = act' } where + act' :: GHC.LogAction +#if __GLASGOW_HASKELL__ >= 800 + act' df _ sev src _ msg = act df sev src msg +#elif __GLASGOW_HASKELL__ == 710 + act' df sev src _ msg = act df sev src msg +#endif + +#if __GLASGOW_HASKELL__ == 710 +instance (Monad m, GHC.HasDynFlags m) => GHC.HasDynFlags (ReaderT r m) where + getDynFlags = lift GHC.getDynFlags +#endif + +flags :: [String] +#if __GLASGOW_HASKELL__ >= 800 +flags = concat [ + [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags], + ["warn-" ++ option | (GHC.FlagSpec option _ _ _) <- GHC.wWarningFlags], + [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]] +#elif __GLASGOW_HASKELL__ >= 710 +flags = concat [ + [option | (GHC.FlagSpec option _ _ _) <- GHC.fFlags], + [option | (GHC.FlagSpec option _ _ _) <- GHC.fWarningFlags], + [option | (GHC.FlagSpec option _ _ _) <- GHC.fLangFlags]] +#elif __GLASGOW_HASKELL__ >= 704 +flags = concat [ + [option | (option, _, _) <- GHC.fFlags], + [option | (option, _, _) <- GHC.fWarningFlags], + [option | (option, _, _) <- GHC.fLangFlags]] +#endif + +#if __GLASGOW_HASKELL__ >= 800 +recSelParent :: IdInfo.RecSelParent -> String +recSelParent (IdInfo.RecSelData p) = Name.getOccString p +recSelParent (IdInfo.RecSelPatSyn p) = Name.getOccString p +#else +recSelParent :: GHC.TyCon -> String +recSelParent = Name.getOccString +#endif + +#if __GLASGOW_HASKELL__ >= 800 +recSelCtors :: IdInfo.RecSelParent -> [String] +recSelCtors (IdInfo.RecSelData p) = map Name.getOccString (GHC.tyConDataCons p) +recSelCtors (IdInfo.RecSelPatSyn p) = [Name.getOccString p] +#else +recSelCtors :: GHC.TyCon -> [String] +recSelCtors = return . Name.getOccString +#endif + +getFixity :: BasicTypes.Fixity -> (Int, BasicTypes.FixityDirection) +#if __GLASGOW_HASKELL__ >= 800 +getFixity (BasicTypes.Fixity _ i d) = (i, d) +#else +getFixity (BasicTypes.Fixity i d) = (i, d) +#endif + +languages :: [String] +languages = GHC.supportedLanguagesAndExtensions + +unqualStyle :: GHC.DynFlags -> PprStyle +#if __GLASGOW_HASKELL__ == 802 +unqualStyle df = mkUserStyle df neverQualify AllTheWay +#else +unqualStyle _ = mkUserStyle neverQualify AllTheWay +#endif + +#if __GLASGOW_HASKELL__ == 802 +exposedModuleName :: (a, Maybe b) -> a +exposedModuleName = fst +#else +exposedModuleName :: GHC.ExposedModule unit mname -> mname +exposedModuleName = GHC.exposedName +#endif
src/HsDev/Tools/Ghc/MGhc.hs view
@@ -1,204 +1,215 @@-{-# LANGUAGE UnicodeSyntax, GeneralizedNewtypeDeriving, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Tools.Ghc.MGhc (- SessionState(..), sessionActive, sessionMap,- MGhcT(..), runMGhcT, liftGhc,- hasSession, findSession, findSessionBy, saveSession,- initSession, newSession,- switchSession, switchSession_,- deleteSession, restoreSession, usingSession- ) where--import Control.Lens-import Control.Monad.Morph-import Control.Monad.Catch-import Control.Monad.Reader-import Control.Monad.State-import Data.Default-import Data.IORef-import Data.List (find)-import Data.Map (Map)-import qualified Data.Map as M-import Data.Maybe (fromMaybe, isJust)-import System.Log.Simple.Monad (MonadLog(..))--import DynFlags-import Exception hiding (catch, mask, uninterruptibleMask, bracket)-import GHC-import GhcMonad-import HscTypes-import Outputable-import SysTools--data SessionState s = SessionState {- _sessionActive :: Maybe s,- _sessionMap :: Map s HscEnv }--instance Default (SessionState s) where- def = SessionState Nothing M.empty--sessionActive :: Lens' (SessionState s) (Maybe s)-sessionActive = lens g s where- g = _sessionActive- s st nm = st { _sessionActive = nm }--sessionMap :: Lens' (SessionState s) (Map s HscEnv)-sessionMap = lens g s where- g = _sessionMap- s st m = st { _sessionMap = m }--instance ExceptionMonad m => ExceptionMonad (StateT s m) where- gcatch act onError = StateT $ \st -> gcatch (runStateT act st) (\e -> runStateT (onError e) st)- gmask f = StateT $ gmask . f' where- f' st' act' = runStateT (f act) st' where- act st = StateT $ act' . runStateT st--instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where- gcatch act onError = ReaderT $ \v -> gcatch (runReaderT act v) (\e -> runReaderT (onError e) v)- gmask f = ReaderT $ gmask . f' where- f' v' act' = runReaderT (f act) v' where- act v = ReaderT $ act' . runReaderT v---- | Multi-session ghc monad-newtype MGhcT s m a = MGhcT { unMGhcT :: GhcT (ReaderT (Maybe FilePath) (StateT (SessionState s) m)) a }- deriving (Functor, Applicative, Monad, MonadIO, ExceptionMonad, HasDynFlags, GhcMonad, MonadState (SessionState s), MonadReader (Maybe FilePath), MonadThrow, MonadCatch, MonadMask, MonadLog)--instance MonadTrans GhcT where- lift = liftGhcT--instance MFunctor GhcT where- hoist fn = GhcT . (fn .) . unGhcT--instance MonadState st m => MonadState st (GhcT m) where- get = lift get- put = lift . put- state = lift . state--instance MonadReader r m => MonadReader r (GhcT m) where- ask = lift ask- local f act = GhcT $ local f . unGhcT act--instance MonadThrow m => MonadThrow (GhcT m) where- throwM = lift . throwM--instance MonadCatch m => MonadCatch (GhcT m) where- catch act onError = GhcT $ \sess -> catch (unGhcT act sess) (flip unGhcT sess . onError)--instance MonadMask m => MonadMask (GhcT m) where- mask f = GhcT $ \s -> mask $ \g -> unGhcT (f $ q g) s where- q g' act = GhcT $ g' . unGhcT act- uninterruptibleMask f = GhcT $ \s -> uninterruptibleMask $ \g -> unGhcT (f $ q g) s where- q g' act = GhcT $ g' . unGhcT act---- | Run multi-session ghc-runMGhcT :: (MonadIO m, ExceptionMonad m, Ord s) => Maybe FilePath -> MGhcT s m a -> m a-runMGhcT lib act = do- ref <- liftIO $ newIORef (panic "empty session")- let- session = Session ref- flip evalStateT def $ flip runReaderT lib $ flip unGhcT session $ unMGhcT $ act `gfinally` cleanup- where- cleanup :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m ()- cleanup = do- void saveSession- sessions <- gets (M.elems . view sessionMap)- liftIO $ mapM_ cleanupSession sessions- modify (set sessionMap M.empty)---- | Lift `Ghc` monad onto `MGhc`-liftGhc :: MonadIO m => Ghc a -> MGhcT s m a-liftGhc (Ghc act) = MGhcT $ GhcT $ liftIO . act---- | Does session exist-hasSession :: (MonadIO m, Ord s) => s -> MGhcT s m Bool-hasSession key = do- msess <- gets (preview (sessionMap . ix key))- return $ isJust msess---- | Find session-findSession :: (MonadIO m, Ord s) => s -> MGhcT s m (Maybe s)-findSession key = do- mkeys <- gets (M.keys . view sessionMap)- return $ find (== key) mkeys---- | Find session by-findSessionBy :: MonadIO m => (s -> Bool) -> MGhcT s m [s]-findSessionBy p = do- mkeys <- gets (M.keys . view sessionMap)- return $ filter p mkeys---- | Save current session-saveSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m (Maybe s)-saveSession = do- key <- gets (view sessionActive)- case key of- Just key' -> do- sess <- getSession- modify (set (sessionMap . at key') (Just sess))- Nothing -> return ()- return key---- | Initialize new session-initSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m ()-initSession = do- lib <- ask- initGhcMonad lib- void saveSession--activateSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m (Maybe HscEnv)-activateSession key = do- void saveSession- modify (set sessionActive $ Just key)- gets (view (sessionMap . at key))---- | Create new named session, deleting existing session-newSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m ()-newSession key = do- msess <- activateSession key- maybe (return ()) (liftIO . cleanupSession) msess- initSession---- | Switch to session, creating if not exist, returns True if session was created-switchSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m Bool-switchSession key = do- msess <- activateSession key- case msess of- Nothing -> initSession >> return True- Just sess -> setSession sess >> return False---- | Switch to session, creating if not exist and initializing with passed function-switchSession_ :: (MonadIO m, ExceptionMonad m, Ord s) => s -> Maybe (MGhcT s m ()) -> MGhcT s m ()-switchSession_ key f = do- new <- switchSession key- when new $ fromMaybe (return ()) f---- | Delete existing session-deleteSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m ()-deleteSession key = do- cur <- saveSession- when (cur == Just key) $- modify (set sessionActive Nothing)- msess <- gets (view (sessionMap . at key))- modify (set (sessionMap . at key) Nothing)- case msess of- Nothing -> return ()- Just sess -> liftIO $ cleanupSession sess---- | Save and restore session-restoreSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => MGhcT s m a -> MGhcT s m a-restoreSession act = bracket saveSession (maybe (return ()) (void . switchSession)) $ const act---- | Run action using session, restoring session back-usingSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => s -> MGhcT s m a -> MGhcT s m a-usingSession key act = restoreSession $ do- void $ switchSession key- act---- | Cleanup session-cleanupSession :: HscEnv -> IO ()-cleanupSession env = do- cleanTempFiles df- cleanTempDirs df- where- df = hsc_dflags env+{-# LANGUAGE UnicodeSyntax, GeneralizedNewtypeDeriving, ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, UndecidableInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Tools.Ghc.MGhc ( + SessionState(..), sessionActive, sessionMap, + MGhcT(..), runMGhcT, liftGhc, + currentSession, hasSession, findSession, findSessionBy, saveSession, + initSession, newSession, + switchSession, switchSession_, + deleteSession, restoreSession, usingSession, tempSession + ) where + +import Control.Lens +import Control.Monad.Morph +import Control.Monad.Catch +import Control.Monad.Reader +import Control.Monad.State +import Data.Default +import Data.IORef +import Data.List (find) +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (fromMaybe, isJust) +import System.Log.Simple.Monad (MonadLog(..)) + +import DynFlags +import Exception hiding (catch, mask, uninterruptibleMask, bracket, finally) +import GHC +import GHCi +import GhcMonad +import HscTypes +import Outputable +import SysTools + +data SessionState s = SessionState { + _sessionActive :: Maybe s, + _sessionMap :: Map s HscEnv } + +instance Default (SessionState s) where + def = SessionState Nothing M.empty + +sessionActive :: Lens' (SessionState s) (Maybe s) +sessionActive = lens g s where + g = _sessionActive + s st nm = st { _sessionActive = nm } + +sessionMap :: Lens' (SessionState s) (Map s HscEnv) +sessionMap = lens g s where + g = _sessionMap + s st m = st { _sessionMap = m } + +instance ExceptionMonad m => ExceptionMonad (StateT s m) where + gcatch act onError = StateT $ \st -> gcatch (runStateT act st) (\e -> runStateT (onError e) st) + gmask f = StateT $ gmask . f' where + f' st' act' = runStateT (f act) st' where + act st = StateT $ act' . runStateT st + +instance ExceptionMonad m => ExceptionMonad (ReaderT r m) where + gcatch act onError = ReaderT $ \v -> gcatch (runReaderT act v) (\e -> runReaderT (onError e) v) + gmask f = ReaderT $ gmask . f' where + f' v' act' = runReaderT (f act) v' where + act v = ReaderT $ act' . runReaderT v + +-- | Multi-session ghc monad +newtype MGhcT s m a = MGhcT { unMGhcT :: GhcT (ReaderT (Maybe FilePath) (StateT (SessionState s) m)) a } + deriving (Functor, Applicative, Monad, MonadIO, ExceptionMonad, HasDynFlags, GhcMonad, MonadState (SessionState s), MonadReader (Maybe FilePath), MonadThrow, MonadCatch, MonadMask, MonadLog) + +instance MonadTrans GhcT where + lift = liftGhcT + +instance MFunctor GhcT where + hoist fn = GhcT . (fn .) . unGhcT + +instance MonadState st m => MonadState st (GhcT m) where + get = lift get + put = lift . put + state = lift . state + +instance MonadReader r m => MonadReader r (GhcT m) where + ask = lift ask + local f act = GhcT $ local f . unGhcT act + +instance MonadThrow m => MonadThrow (GhcT m) where + throwM = lift . throwM + +instance MonadCatch m => MonadCatch (GhcT m) where + catch act onError = GhcT $ \sess -> catch (unGhcT act sess) (flip unGhcT sess . onError) + +instance MonadMask m => MonadMask (GhcT m) where + mask f = GhcT $ \s -> mask $ \g -> unGhcT (f $ q g) s where + q g' act = GhcT $ g' . unGhcT act + uninterruptibleMask f = GhcT $ \s -> uninterruptibleMask $ \g -> unGhcT (f $ q g) s where + q g' act = GhcT $ g' . unGhcT act + +-- | Run multi-session ghc +runMGhcT :: (MonadIO m, ExceptionMonad m, Ord s) => Maybe FilePath -> MGhcT s m a -> m a +runMGhcT lib act = do + ref <- liftIO $ newIORef (panic "empty session") + let + session = Session ref + flip evalStateT def $ flip runReaderT lib $ flip unGhcT session $ unMGhcT $ act `gfinally` cleanup + where + cleanup :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m () + cleanup = do + void saveSession + sessions <- gets (M.elems . view sessionMap) + liftIO $ mapM_ cleanupSession sessions + modify (set sessionMap M.empty) + +-- | Lift `Ghc` monad onto `MGhc` +liftGhc :: MonadIO m => Ghc a -> MGhcT s m a +liftGhc (Ghc act) = MGhcT $ GhcT $ liftIO . act + +currentSession :: MonadIO m => MGhcT s m (Maybe s) +currentSession = gets (view sessionActive) + +-- | Does session exist +hasSession :: (MonadIO m, Ord s) => s -> MGhcT s m Bool +hasSession key = do + msess <- gets (preview (sessionMap . ix key)) + return $ isJust msess + +-- | Find session +findSession :: (MonadIO m, Ord s) => s -> MGhcT s m (Maybe s) +findSession key = do + mkeys <- gets (M.keys . view sessionMap) + return $ find (== key) mkeys + +-- | Find session by +findSessionBy :: MonadIO m => (s -> Bool) -> MGhcT s m [s] +findSessionBy p = do + mkeys <- gets (M.keys . view sessionMap) + return $ filter p mkeys + +-- | Save current session +saveSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m (Maybe s) +saveSession = do + key <- currentSession + case key of + Just key' -> do + sess <- getSession + modify (set (sessionMap . at key') (Just sess)) + Nothing -> return () + return key + +-- | Initialize new session +initSession :: (MonadIO m, ExceptionMonad m, Ord s) => MGhcT s m () +initSession = do + lib <- ask + initGhcMonad lib + void saveSession + +activateSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m (Maybe HscEnv) +activateSession key = do + void saveSession + modify (set sessionActive $ Just key) + gets (view (sessionMap . at key)) + +-- | Create new named session, deleting existing session +newSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m () +newSession key = do + msess <- activateSession key + maybe (return ()) (liftIO . cleanupSession) msess + initSession + +-- | Switch to session, creating if not exist, returns True if session was created +switchSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m Bool +switchSession key = do + msess <- activateSession key + case msess of + Nothing -> initSession >> return True + Just sess -> setSession sess >> return False + +-- | Switch to session, creating if not exist and initializing with passed function +switchSession_ :: (MonadIO m, ExceptionMonad m, Ord s) => s -> Maybe (MGhcT s m ()) -> MGhcT s m () +switchSession_ key f = do + new <- switchSession key + when new $ fromMaybe (return ()) f + +-- | Delete existing session +deleteSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m () +deleteSession key = do + cur <- saveSession + when (cur == Just key) $ + modify (set sessionActive Nothing) + msess <- gets (view (sessionMap . at key)) + modify (set (sessionMap . at key) Nothing) + case msess of + Nothing -> return () + Just sess -> liftIO $ cleanupSession sess + +-- | Save and restore session +restoreSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => MGhcT s m a -> MGhcT s m a +restoreSession act = bracket saveSession (maybe (return ()) (void . switchSession)) $ const act + +-- | Run action using session, restoring session back +usingSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => s -> MGhcT s m a -> MGhcT s m a +usingSession key act = restoreSession $ do + void $ switchSession key + act + +-- | Run with temporary session, like @usingSession@, but deletes self session +tempSession :: (MonadIO m, MonadMask m, ExceptionMonad m, Ord s) => s -> MGhcT s m a -> MGhcT s m a +tempSession key act = do + exist' <- hasSession key + usingSession key act `finally` when (not exist') (deleteSession key) + +-- | Cleanup session +cleanupSession :: HscEnv -> IO () +cleanupSession env = do + cleanTempFiles df + cleanTempDirs df + stopIServ env + where + df = hsc_dflags env
src/HsDev/Tools/Ghc/Prelude.hs view
@@ -1,5 +1,5 @@ module HsDev.Tools.Ghc.Prelude ( - reduce, one, trim, + reduce, trim, -- * Regexes rx, srx, splitRx, -- * Case @@ -21,10 +21,6 @@ -- | Reduce list to one element reduce :: ([a] -> a) -> [a] -> [a] reduce = (return .) - --- | Make list from single element -one :: a -> [a] -one = return -- | Trim string trim :: String -> String
src/HsDev/Tools/Ghc/Session.hs view
@@ -1,49 +1,42 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Tools.Ghc.Session (- ghcSession, ghciSession, haddockSession, targetSession, interpretModule,-- module HsDev.Tools.Ghc.Worker- ) where--import Control.Lens-import Data.Text (unpack)-import System.FilePath--import Control.Concurrent.Worker-import HsDev.Symbols.Types-import HsDev.Sandbox (getModuleOpts)-import HsDev.Tools.Ghc.Worker--import qualified GHC---- | Get ghc session-ghcSession :: [String] -> GhcM ()-ghcSession = workerSession . SessionGhc---- | Get ghci session-ghciSession :: GhcM ()-ghciSession = workerSession SessionGhci---- | Get haddock session with flags-haddockSession :: [String] -> GhcM ()-haddockSession opts = ghcSession ("-haddock" : opts)---- | Session for module-targetSession :: [String] -> Module -> GhcM ()-targetSession opts m = do- opts' <- getModuleOpts opts m- ghcSession ("-Wall" : opts')---- | Interpret file-interpretModule :: Module -> Maybe String -> GhcM ()-interpretModule m mcts = do- targetSession [] m- let- f = preview (moduleLocation . moduleFile) m- case f of- Nothing -> return ()- Just f' -> withCurrentDirectory (takeDirectory f') $ do- t <- makeTarget (takeFileName f') mcts- loadTargets [t]- GHC.setContext [GHC.IIModule $ GHC.mkModuleName $ unpack $ view moduleName m]+{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Tools.Ghc.Session ( + targetSession, interpretModule, + + module HsDev.Tools.Ghc.Worker + ) where + +import Control.Lens +import Data.Maybe (isJust) +import Data.Text (Text, unpack) +import System.Log.Simple + +import Control.Concurrent.Worker +import System.Directory.Paths +import HsDev.Symbols.Types +import HsDev.Sandbox (getModuleOpts) +import HsDev.Tools.Ghc.Worker + +import qualified GHC + +-- | Session for module +targetSession :: [String] -> Module -> GhcM () +targetSession opts m = do + opts' <- getModuleOpts opts m + ghcSession ("-Wall" : opts') + +-- | Interpret file +interpretModule :: Module -> Maybe Text -> GhcM () +interpretModule m mcts + | isJust mpath = do + let + rootDir = maybe (takeDir fpath) (view projectPath) (m ^? moduleId . moduleLocation . moduleProject . _Just) + withCurrentDirectory (view path rootDir) $ do + t <- makeTarget (relPathTo rootDir fpath) mcts + loadTargets [t] + GHC.setContext [GHC.IIModule . GHC.mkModuleName . unpack . view (moduleId . moduleName) $ m] + | otherwise = return () + where + mpath = m ^? moduleId . moduleLocation . moduleFile + Just fpath = mpath
src/HsDev/Tools/Ghc/Types.hs view
@@ -1,143 +1,141 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, RankNTypes, TemplateHaskell, OverloadedStrings #-}--module HsDev.Tools.Ghc.Types (- TypedExpr(..), typedExpr, typedType,- moduleTypes, fileTypes,- setModuleTypes, inferTypes- ) where--import Control.DeepSeq-import Control.Lens (over, view, set, each, preview, makeLenses, _Just)-import Control.Monad-import Control.Monad.IO.Class-import Data.Aeson-import Data.Generics-import Data.List (find)-import Data.Maybe-import Data.String (fromString)-import System.Directory-import System.FilePath-import System.Log.Simple (MonadLog(..), scope)--import GHC hiding (exprType, Module, moduleName)-import GHC.SYB.Utils (everythingStaged, Stage(TypeChecker))-import GhcPlugins (mkFunTys)-import CoreUtils-import Desugar (deSugarExpr)-import TcHsSyn (hsPatType)-import Outputable-import PprTyThing-import qualified Pretty--import System.Directory.Paths (canonicalize)-import HsDev.Error-import HsDev.Symbols-import HsDev.Tools.Ghc.Worker as Ghc-import HsDev.Tools.Ghc.Compat-import HsDev.Tools.Types-import HsDev.Util--class HasType a where- getType :: GhcMonad m => TypecheckedModule -> a -> m (Maybe (SrcSpan, Type))--instance HasType (LHsExpr Id) where- getType _ e = do- env <- getSession- mbe <- liftIO $ liftM snd $ deSugarExpr env e- return $ do- ex <- mbe- return (getLoc e, exprType ex)--instance HasType (LHsBind Id) where- getType _ (L spn FunBind { fun_matches = m}) = return $ Just (spn, typ) where- typ = mkFunTys (mg_arg_tys m) (mg_res_ty m)- getType _ _ = return Nothing--instance HasType (LPat Id) where- getType _ (L spn pat) = return $ Just (spn, hsPatType pat)--locatedTypes :: Typeable a => TypecheckedSource -> [Located a]-locatedTypes = types' p where- types' :: Typeable r => (r -> Bool) -> GenericQ [r]- types' p' = everythingStaged TypeChecker (++) [] ([] `mkQ` (\x -> [x | p' x]))- p (L spn _) = isGoodSrcSpan spn--moduleTypes :: GhcMonad m => FilePath -> m [(SrcSpan, Type)]-moduleTypes fpath = do- fpath' <- liftIO $ canonicalize fpath- mg <- getModuleGraph- [m] <- liftIO $ flip filterM mg $ \m -> do- mfile <- traverse (liftIO . canonicalize) $ ml_hs_file (ms_location m)- return (Just fpath' == mfile)- p <- parseModule m- tm <- typecheckModule p- let- ts = tm_typechecked_source tm- liftM (catMaybes . concat) $ sequence [- mapM (getType tm) (locatedTypes ts :: [LHsBind Id]),- mapM (getType tm) (locatedTypes ts :: [LHsExpr Id]),- mapM (getType tm) (locatedTypes ts :: [LPat Id])]--data TypedExpr = TypedExpr {- _typedExpr :: String,- _typedType :: String }- deriving (Eq, Ord, Read, Show)--makeLenses ''TypedExpr--instance NFData TypedExpr where- rnf (TypedExpr e t) = rnf e `seq` rnf t--instance ToJSON TypedExpr where- toJSON (TypedExpr e t) = object [- "expr" .= e,- "type" .= t]--instance FromJSON TypedExpr where- parseJSON = withObject "typed-expr" $ \v -> TypedExpr <$>- v .:: "expr" <*>- v .:: "type"---- | Get all types in module-fileTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m [Note TypedExpr]-fileTypes opts m msrc = scope "types" $ case view moduleLocation m of- FileModule file proj -> do- file' <- liftIO $ canonicalize file- cts <- maybe (liftIO $ readFileUtf8 file') return msrc- let- dir = fromMaybe- (sourceModuleRoot (view moduleName m) file') $- preview (_Just . projectPath) proj- dirExist <- liftIO $ doesDirectoryExist dir- withFlags $ (if dirExist then Ghc.withCurrentDirectory dir else id) $ do- addCmdOpts opts- target <- makeTarget (makeRelative dir file') msrc- loadTargets [target]- ts <- moduleTypes file'- df <- getSessionDynFlags- return $ map (setExpr cts . recalcTabs cts 8 . uncurry (toNote df)) ts- _ -> hsdevError $ ModuleNotSource (view moduleLocation m)- where- toNote :: DynFlags -> SrcSpan -> Type -> Note String- toNote df spn tp = Note {- _noteSource = noLocation,- _noteRegion = spanRegion spn,- _noteLevel = Nothing,- _note = showType df tp }- setExpr :: String -> Note String -> Note TypedExpr- setExpr cts n = over note (TypedExpr (regionStr (view noteRegion n) cts)) n- showType :: DynFlags -> Type -> String- showType df = renderStyle Pretty.OneLineMode 80 . withPprStyleDoc df (unqualStyle df) . pprTypeForUser---- | Set types to module-setModuleTypes :: [Note TypedExpr] -> Module -> Module-setModuleTypes ts = over (moduleDeclarations . each) setType where- setType :: Declaration -> Declaration- setType d = fromMaybe d $ do- pos <- view declarationPosition d- tnote <- find ((== pos) . view (noteRegion . regionFrom)) ts- return $ set (declaration . functionType) (Just $ fromString $ view (note . typedType) tnote) d---- | Infer types in module-inferTypes :: (MonadLog m, GhcMonad m) => [String] -> Module -> Maybe String -> m Module-inferTypes opts m msrc = scope "infer" $ liftM (`setModuleTypes` m) $ fileTypes opts m msrc+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, RankNTypes, TemplateHaskell, OverloadedStrings #-} + +module HsDev.Tools.Ghc.Types ( + TypedExpr(..), typedExpr, typedType, + moduleTypes, fileTypes, + setModuleTypes, inferTypes + ) where + +import Control.DeepSeq +import Control.Lens (over, view, set, each, preview, makeLenses, _Just) +import Control.Monad +import Control.Monad.IO.Class +import Data.Aeson +import Data.Generics +import Data.List (find) +import Data.Maybe +import Data.String (fromString) +import Data.Text (Text) +import System.Log.Simple (MonadLog(..), scope) + +import GHC hiding (exprType, Module, moduleName) +import GHC.SYB.Utils (everythingStaged, Stage(TypeChecker)) +import GhcPlugins (mkFunTys) +import CoreUtils +import Desugar (deSugarExpr) +import TcHsSyn (hsPatType) +import Outputable +import PprTyThing +import qualified Pretty + +import System.Directory.Paths +import HsDev.Error +import HsDev.Symbols +import HsDev.Tools.Ghc.Worker as Ghc +import HsDev.Tools.Ghc.Compat +import HsDev.Tools.Types +import HsDev.Util + +class HasType a where + getType :: GhcMonad m => TypecheckedModule -> a -> m (Maybe (SrcSpan, Type)) + +instance HasType (LHsExpr Id) where + getType _ e = do + env <- getSession + mbe <- liftIO $ liftM snd $ deSugarExpr env e + return $ do + ex <- mbe + return (getLoc e, exprType ex) + +instance HasType (LHsBind Id) where + getType _ (L _ FunBind { fun_id = fid, fun_matches = m}) = return $ Just (getLoc fid, typ) where + typ = mkFunTys (mg_arg_tys m) (mg_res_ty m) + getType _ _ = return Nothing + +instance HasType (LPat Id) where + getType _ (L spn pat) = return $ Just (spn, hsPatType pat) + +locatedTypes :: Typeable a => TypecheckedSource -> [Located a] +locatedTypes = types' p where + types' :: Typeable r => (r -> Bool) -> GenericQ [r] + types' p' = everythingStaged TypeChecker (++) [] ([] `mkQ` (\x -> [x | p' x])) + p (L spn _) = isGoodSrcSpan spn + +moduleTypes :: GhcMonad m => Path -> m [(SrcSpan, Type)] +moduleTypes fpath = do + fpath' <- liftIO $ canonicalize fpath + mg <- getModuleGraph + [m] <- liftIO $ flip filterM mg $ \m -> do + mfile <- traverse (liftIO . canonicalize) $ ml_hs_file (ms_location m) + return (Just (view path fpath') == mfile) + p <- parseModule m + tm <- typecheckModule p + let + ts = tm_typechecked_source tm + liftM (catMaybes . concat) $ sequence [ + mapM (getType tm) (locatedTypes ts :: [LHsBind Id]), + mapM (getType tm) (locatedTypes ts :: [LHsExpr Id]), + mapM (getType tm) (locatedTypes ts :: [LPat Id])] + +data TypedExpr = TypedExpr { + _typedExpr :: Maybe Text, + _typedType :: Text } + deriving (Eq, Ord, Read, Show) + +makeLenses ''TypedExpr + +instance NFData TypedExpr where + rnf (TypedExpr e t) = rnf e `seq` rnf t + +instance ToJSON TypedExpr where + toJSON (TypedExpr e t) = object $ noNulls [ + "expr" .= e, + "type" .= t] + +instance FromJSON TypedExpr where + parseJSON = withObject "typed-expr" $ \v -> TypedExpr <$> + v .::? "expr" <*> + v .:: "type" + +-- | Get all types in module +fileTypes :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m [Note TypedExpr] +fileTypes m msrc = scope "types" $ case view (moduleId . moduleLocation) m of + FileModule file proj -> do + file' <- liftIO $ canonicalize file + cts <- maybe (liftIO $ readFileUtf8 (view path file')) return msrc + let + dir = fromMaybe + (sourceModuleRoot (view (moduleId . moduleName) m) file') $ + preview (_Just . projectPath) proj + ex <- liftIO $ dirExists dir + (if ex then Ghc.withCurrentDirectory (view path dir) else id) $ do + target <- makeTarget (relPathTo dir file') msrc + loadTargets [target] + ts <- moduleTypes file' + df <- getSessionDynFlags + return $ map (setExpr cts . recalcTabs cts 8 . uncurry (toNote df)) ts + _ -> hsdevError $ ModuleNotSource (view (moduleId . moduleLocation) m) + where + toNote :: DynFlags -> SrcSpan -> Type -> Note Text + toNote df spn tp = Note { + _noteSource = noLocation, + _noteRegion = spanRegion spn, + _noteLevel = Nothing, + _note = fromString $ showType df tp } + setExpr :: Text -> Note Text -> Note TypedExpr + setExpr cts n = over note (TypedExpr (Just (regionStr (view noteRegion n) cts))) n + showType :: DynFlags -> Type -> String + showType df = renderStyle Pretty.OneLineMode 80 . withPprStyleDoc df (unqualStyle df) . pprTypeForUser + +-- | Set types to module +setModuleTypes :: [Note TypedExpr] -> Module -> Module +setModuleTypes ts = over (moduleScope . each . each) setType . over (moduleExports . each) setType where + setType :: Symbol -> Symbol + setType d = fromMaybe d $ do + pos <- view symbolPosition d + tnote <- find ((== pos) . view (noteRegion . regionFrom)) ts + return $ set (symbolInfo . functionType) (Just $ view (note . typedType) tnote) d + +-- | Infer types in module +inferTypes :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m Module +inferTypes m msrc = scope "infer" $ liftM (`setModuleTypes` m) $ fileTypes m msrc
src/HsDev/Tools/Ghc/Worker.hs view
@@ -1,249 +1,267 @@-{-# LANGUAGE PatternGuards, OverloadedStrings, FlexibleContexts #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Tools.Ghc.Worker (- -- * Workers- SessionTarget(..),- GhcM, GhcWorker, MGhcT(..), runGhcM,- ghcWorker,- workerSession,-- -- * Initializers and actions- ghcRun,- withFlags, modifyFlags, addCmdOpts, setCmdOpts,- importModules, preludeModules,- evaluate,- clearTargets, makeTarget, loadTargets,- -- * Utils- listPackages, spanRegion,- withCurrentDirectory,- logToChan, logToNull,-- Ghc,- LogT(..),-- module HsDev.Tools.Ghc.MGhc,- module Control.Concurrent.Worker- ) where--import Control.Monad-import Control.Monad.Except-import Control.Monad.Reader-import Control.Monad.Catch-import Data.Dynamic-import Data.List (intercalate)-import Data.Maybe-import Data.Time.Clock (getCurrentTime)-import Data.Version (showVersion)-import System.Directory (getCurrentDirectory, setCurrentDirectory)-import qualified System.Log.Simple as Log-import System.Log.Simple.Monad (MonadLog(..), LogT(..), withLog)-import Text.Read (readMaybe)-import Text.Format hiding (withFlags)--import Exception (ExceptionMonad(..))-import GHC hiding (Warning, Module, moduleName, pkgDatabase)-import GHC.Paths-import Outputable-import FastString (unpackFS)-import Packages-import StringBuffer--import Control.Concurrent.FiniteChan-import Control.Concurrent.Worker-import System.Directory.Paths-import HsDev.Symbols.Location (Position(..), Region(..), region, ModulePackage, ModuleLocation(..))-import HsDev.Tools.Types-import HsDev.Tools.Ghc.Compat hiding (setLogAction)-import qualified HsDev.Tools.Ghc.Compat as C (setLogAction)-import HsDev.Tools.Ghc.MGhc--data SessionTarget =- SessionGhci |- SessionGhc [String]--instance Show SessionTarget where- show SessionGhci = "ghci"- show (SessionGhc opts) = "ghc " ++ intercalate ", " opts--instance Formattable SessionTarget--instance Eq SessionTarget where- SessionGhci == SessionGhci = True- SessionGhc lopts == SessionGhc ropts = lopts == ropts- _ == _ = False--instance Ord SessionTarget where- compare l r = compare (isGhci l) (isGhci r) where- isGhci SessionGhci = True- isGhci _ = False--type GhcM a = MGhcT SessionTarget (LogT IO) a--type GhcWorker = Worker (MGhcT SessionTarget (LogT IO))--instance (Monad m, GhcMonad m) => GhcMonad (ReaderT r m) where- getSession = lift getSession- setSession = lift . setSession--instance ExceptionMonad m => ExceptionMonad (LogT m) where- gcatch act onError = LogT $ gcatch (runLogT act) (runLogT . onError)- gmask f = LogT $ gmask f' where- f' g' = runLogT $ f (LogT . g' . runLogT)--instance MonadThrow Ghc where- throwM = liftIO . throwM--runGhcM :: MonadLog m => Maybe FilePath -> GhcM a -> m a-runGhcM dir act = do- l <- Log.askLog- liftIO $ withLog l $ runMGhcT dir act---- | Multi-session ghc worker-ghcWorker :: MonadLog m => m GhcWorker-ghcWorker = do- l <- Log.askLog- liftIO $ startWorker (withLog l . runGhcM (Just libdir)) id (Log.scope "ghc")---- | Create session with options-workerSession :: SessionTarget -> GhcM ()-workerSession SessionGhci = do- Log.sendLog Log.Trace $ "session: {}" ~~ SessionGhci- switchSession_ SessionGhci $ Just $ ghcRun [] (importModules preludeModules)-workerSession s@(SessionGhc opts) = do- ms <- findSessionBy isGhcSession- forM_ ms $ \s'@(SessionGhc opts') -> when (opts /= opts') $ do- Log.sendLog Log.Trace $ "killing session: {}" ~~ s'- deleteSession s'- Log.sendLog Log.Trace $ "session: {}" ~~ s- switchSession_ s $ Just $ ghcRun opts (return ())- where- isGhcSession (SessionGhc _) = True- isGhcSession _ = False---- | Run ghc-ghcRun :: GhcMonad m => [String] -> m a -> m a-ghcRun opts f = do- fs <- getSessionDynFlags- cleanupHandler fs $ do- (fs', _, _) <- parseDynamicFlags fs (map noLoc opts)- let fs'' = fs' {- ghcMode = CompManager,- ghcLink = LinkInMemory,- hscTarget = HscInterpreted }- -- ghcLink = NoLink,- -- hscTarget = HscNothing }- void $ setSessionDynFlags fs''- modifyFlags $ C.setLogAction logToNull- f---- | Alter @DynFlags@ temporary-withFlags :: GhcMonad m => m a -> m a-withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const---- | Update @DynFlags@-modifyFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m ()-modifyFlags f = do- fs <- getSessionDynFlags- let- fs' = f fs- _ <- setSessionDynFlags fs'- _ <- liftIO $ initPackages fs'- return ()---- | Add options without reinit session-addCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()-addCmdOpts opts = do- Log.sendLog Log.Trace $ "setting ghc options: {}" ~~ unwords opts- fs <- getSessionDynFlags- (fs', _, _) <- parseDynamicFlags fs (map noLoc opts)- let fs'' = fs' {- ghcMode = CompManager,- ghcLink = LinkInMemory,- hscTarget = HscInterpreted }- -- ghcLink = NoLink,- -- hscTarget = HscNothing }- void $ setSessionDynFlags fs''---- | Set options after session reinit-setCmdOpts :: (MonadLog m, GhcMonad m) => [String] -> m ()-setCmdOpts opts = do- Log.sendLog Log.Trace $ "restarting ghc session with: {}" ~~ unwords opts- initGhcMonad (Just libdir)- addCmdOpts opts- modifyFlags $ C.setLogAction logToNull---- | Import some modules-importModules :: GhcMonad m => [String] -> m ()-importModules mods = mapM parseImportDecl ["import " ++ m | m <- mods] >>= setContext . map IIDecl---- | Default interpreter modules-preludeModules :: [String]-preludeModules = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"]---- | Evaluate expression-evaluate :: GhcMonad m => String -> m String-evaluate expr = liftM fromDynamic (dynCompileExpr $ "show ({})" ~~ expr) >>=- maybe (fail "evaluate fail") return---- | Clear loaded targets-clearTargets :: GhcMonad m => m ()-clearTargets = loadTargets []---- | Make target with its source code optional-makeTarget :: GhcMonad m => String -> Maybe String -> m Target-makeTarget name Nothing = guessTarget name Nothing-makeTarget name (Just cts) = do- t <- guessTarget name Nothing- tm <- liftIO getCurrentTime- return t { targetContents = Just (stringToStringBuffer cts, tm) }---- | Load all targets-loadTargets :: GhcMonad m => [Target] -> m ()-loadTargets ts = setTargets ts >> load LoadAllTargets >> return ()---- | Get list of installed packages-listPackages :: GhcMonad m => m [ModulePackage]-listPackages = liftM (mapMaybe readPackage . fromMaybe [] . pkgDatabase) getSessionDynFlags--readPackage :: PackageConfig -> Maybe ModulePackage-readPackage pc = readMaybe $ packageNameString pc ++ "-" ++ showVersion (packageVersion pc)---- | Get region of @SrcSpan@-spanRegion :: SrcSpan -> Region-spanRegion (RealSrcSpan s) = Position (srcSpanStartLine s) (srcSpanStartCol s) `region` Position (srcSpanEndLine s) (srcSpanEndCol s)-spanRegion _ = Position 0 0 `region` Position 0 0---- | Set current directory and restore it after action-withCurrentDirectory :: GhcMonad m => FilePath -> m a -> m a-withCurrentDirectory dir act = gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $- const (liftIO (setCurrentDirectory dir) >> act)---- | Log ghc warnings and errors as to chan--- You may have to apply recalcTabs on result notes-logToChan :: Chan (Note OutputMessage) -> LogAction-logToChan ch fs sev src msg- | Just sev' <- checkSev sev = do- src' <- canonicalize srcMod- putChan ch $ Note {- _noteSource = src',- _noteRegion = spanRegion src,- _noteLevel = Just sev',- _note = OutputMessage {- _message = showSDoc fs msg,- _messageSuggestion = Nothing } }- | otherwise = return ()- where- checkSev SevWarning = Just Warning- checkSev SevError = Just Error- checkSev SevFatal = Just Error- checkSev _ = Nothing- srcMod = case src of- RealSrcSpan s' -> FileModule (unpackFS $ srcSpanFile s') Nothing- _ -> ModuleSource Nothing---- | Don't log ghc warnings and errors-logToNull :: LogAction-logToNull _ _ _ _ = return ()---- TODO: Load target by @ModuleLocation@, which may cause updating @DynFlags@+{-# LANGUAGE PatternGuards, OverloadedStrings, FlexibleContexts #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Tools.Ghc.Worker ( + -- * Workers + SessionType(..), SessionConfig(..), + GhcM, GhcWorker, MGhcT(..), runGhcM, + ghcWorker, + workerSession, ghcSession, ghciSession, haddockSession, tmpSession, + + -- * Initializers and actions + ghcRun, ghcRunWith, interpretedFlags, noLinkFlags, + withFlags, modifyFlags, + importModules, preludeModules, + evaluate, + clearTargets, makeTarget, loadTargets, + loadInteractive, reload, + -- * Utils + spanRegion, + withCurrentDirectory, + logToChan, logToNull, + + Ghc, + LogT(..), + + module HsDev.Tools.Ghc.MGhc, + module Control.Concurrent.Worker + ) where + +import Control.Lens (view, over) +import Control.Monad +import Control.Monad.Except +import Control.Monad.Reader +import Control.Monad.Catch +import Data.Dynamic +import Data.Time.Clock (getCurrentTime) +import Data.String (fromString) +import Data.Text (Text) +import qualified Data.Text as T +import System.Directory (getCurrentDirectory, setCurrentDirectory) +import System.FilePath +import qualified System.Log.Simple as Log +import System.Log.Simple.Monad (MonadLog(..), LogT(..), withLog) +import Text.Format hiding (withFlags) + +import Exception (ExceptionMonad(..), ghandle) +import GHC hiding (Warning, Module) +import GHC.Paths +import Outputable +import FastString (unpackFS) +import StringBuffer + +import Control.Concurrent.FiniteChan +import Control.Concurrent.Worker +import System.Directory.Paths +import HsDev.Symbols.Location (Position(..), Region(..), region, ModuleLocation(..)) +import HsDev.Tools.Types +import HsDev.Tools.Ghc.Compat +import qualified HsDev.Tools.Ghc.Compat as C (setLogAction) +import HsDev.Tools.Ghc.MGhc + +data SessionType = SessionGhci | SessionGhc | SessionHaddock | SessionTmp deriving (Eq, Ord) +data SessionConfig = SessionConfig SessionType [String] deriving (Eq, Ord) + +instance Show SessionType where + show SessionGhci = "ghci" + show SessionGhc = "ghc" + show SessionHaddock = "haddock" + show SessionTmp = "tmp" + +instance Show SessionConfig where + show (SessionConfig t opts) = unwords (show t : opts) + +instance Formattable SessionConfig + +type GhcM a = MGhcT SessionConfig (LogT IO) a + +type GhcWorker = Worker (MGhcT SessionConfig (LogT IO)) + +instance (Monad m, GhcMonad m) => GhcMonad (ReaderT r m) where + getSession = lift getSession + setSession = lift . setSession + +instance ExceptionMonad m => ExceptionMonad (LogT m) where + gcatch act onError = LogT $ gcatch (runLogT act) (runLogT . onError) + gmask f = LogT $ gmask f' where + f' g' = runLogT $ f (LogT . g' . runLogT) + +instance MonadThrow Ghc where + throwM = liftIO . throwM + +runGhcM :: MonadLog m => Maybe FilePath -> GhcM a -> m a +runGhcM dir act = do + l <- Log.askLog + liftIO $ withLog l $ runMGhcT dir act + +-- | Multi-session ghc worker +ghcWorker :: MonadLog m => m GhcWorker +ghcWorker = do + l <- Log.askLog + liftIO $ startWorker (withLog l . runGhcM (Just libdir)) (Log.scope "ghc") (ghandle logErr) + where + logErr :: MonadLog m => SomeException -> m () + logErr e = Log.sendLog Log.Warning ("exception in ghc worker task: {}" ~~ displayException e) + +-- | Create session with options +workerSession :: SessionType -> [String] -> GhcM () +workerSession ty opts = do + ms <- findSessionBy toKill + forM_ ms $ \s' -> do + Log.sendLog Log.Trace $ "killing session: {}" ~~ s' + deleteSession s' + Log.sendLog Log.Trace $ "session: {}" ~~ SessionConfig ty opts + switchSession_ (SessionConfig ty opts) $ Just initialize + where + toKill (SessionConfig ty' opts') = or [ + (ty == ty' && opts /= opts'), + (ty /= ty' && ty' `elem` [SessionTmp, SessionHaddock] && ty /= SessionTmp)] + initialize = case ty of + SessionGhci -> ghcRun opts (importModules preludeModules) + SessionGhc -> ghcRun opts (return ()) + SessionTmp -> ghcRun opts (return ()) + SessionHaddock -> ghcRunWith noLinkFlags ("-haddock" : opts) (return ()) + +-- | Get ghc session +ghcSession :: [String] -> GhcM () +ghcSession = workerSession SessionGhc + +-- | Get ghci session +ghciSession :: GhcM () +ghciSession = workerSession SessionGhci [] + +-- | Get haddock session with flags +haddockSession :: [String] -> GhcM () +haddockSession = workerSession SessionHaddock + +-- | Get haddock session with flags +tmpSession :: [String] -> GhcM () +tmpSession = workerSession SessionTmp + +-- | Run ghc +ghcRun :: GhcMonad m => [String] -> m a -> m a +ghcRun = ghcRunWith interpretedFlags + +-- | Run ghc +ghcRunWith :: GhcMonad m => (DynFlags -> DynFlags) -> [String] -> m a -> m a +ghcRunWith onFlags opts act = do + fs <- getSessionDynFlags + cleanupHandler fs $ do + (fs', _, _) <- parseDynamicFlags fs (map noLoc opts) + void $ setSessionDynFlags $ onFlags fs' + modifyFlags $ C.setLogAction logToNull + act + +interpretedFlags :: DynFlags -> DynFlags +interpretedFlags fs = fs { + ghcMode = CompManager, + ghcLink = LinkInMemory, + hscTarget = HscInterpreted } + +noLinkFlags :: DynFlags -> DynFlags +noLinkFlags fs = fs { + ghcMode = CompManager, + ghcLink = NoLink, + hscTarget = HscNothing } + +-- | Alter @DynFlags@ temporary +withFlags :: GhcMonad m => m a -> m a +withFlags = gbracket getSessionDynFlags (\fs -> setSessionDynFlags fs >> return ()) . const + +-- | Update @DynFlags@ +modifyFlags :: GhcMonad m => (DynFlags -> DynFlags) -> m () +modifyFlags f = do + fs <- getSessionDynFlags + let + fs' = f fs + _ <- setSessionDynFlags fs' + -- _ <- liftIO $ initPackages fs' + return () + +-- | Import some modules +importModules :: GhcMonad m => [String] -> m () +importModules mods = mapM parseImportDecl ["import " ++ m | m <- mods] >>= setContext . map IIDecl + +-- | Default interpreter modules +preludeModules :: [String] +preludeModules = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"] + +-- | Evaluate expression +evaluate :: GhcMonad m => String -> m String +evaluate expr = liftM fromDynamic (dynCompileExpr $ "show ({})" ~~ expr) >>= + maybe (fail "evaluate fail") return + +-- | Clear loaded targets +clearTargets :: GhcMonad m => m () +clearTargets = loadTargets [] + +-- | Make target with its source code optional +makeTarget :: GhcMonad m => Text -> Maybe Text -> m Target +makeTarget name Nothing = guessTarget (T.unpack name) Nothing +makeTarget name (Just cts) = do + t <- guessTarget (T.unpack name) Nothing + tm <- liftIO getCurrentTime + return t { targetContents = Just (stringToStringBuffer $ T.unpack cts, tm) } + +-- | Load all targets +loadTargets :: GhcMonad m => [Target] -> m () +loadTargets ts = setTargets ts >> load LoadAllTargets >> return () + +-- | Load and set interactive context +loadInteractive :: GhcMonad m => Path -> Maybe Text -> m () +loadInteractive fpath mcts = do + fpath' <- liftIO $ canonicalize fpath + withCurrentDirectory (view path $ takeDir fpath') $ do + t <- makeTarget (over path takeFileName fpath') mcts + loadTargets [t] + g <- getModuleGraph + setContext [IIModule (ms_mod_name m) | m <- g] + +-- | Reload targets +reload :: GhcMonad m => m () +reload = do + ts <- getTargets + ctx <- getContext + setContext [] + clearTargets + setTargets ts + setContext ctx + +-- | Get region of @SrcSpan@ +spanRegion :: SrcSpan -> Region +spanRegion (RealSrcSpan s) = Position (srcSpanStartLine s) (srcSpanStartCol s) `region` Position (srcSpanEndLine s) (srcSpanEndCol s) +spanRegion _ = Position 0 0 `region` Position 0 0 + +-- | Set current directory and restore it after action +withCurrentDirectory :: GhcMonad m => FilePath -> m a -> m a +withCurrentDirectory dir act = gbracket (liftIO getCurrentDirectory) (liftIO . setCurrentDirectory) $ + const (liftIO (setCurrentDirectory dir) >> act) + +-- | Log ghc warnings and errors as to chan +-- You may have to apply recalcTabs on result notes +logToChan :: Chan (Note OutputMessage) -> LogAction +logToChan ch fs sev src msg + | Just sev' <- checkSev sev = do + src' <- canonicalize srcMod + void $ sendChan ch Note { + _noteSource = src', + _noteRegion = spanRegion src, + _noteLevel = Just sev', + _note = OutputMessage { + _message = fromString $ showSDoc fs msg, + _messageSuggestion = Nothing } } + | otherwise = return () + where + checkSev SevWarning = Just Warning + checkSev SevError = Just Error + checkSev SevFatal = Just Error + checkSev _ = Nothing + srcMod = case src of + RealSrcSpan s' -> FileModule (fromFilePath $ unpackFS $ srcSpanFile s') Nothing + _ -> NoLocation + +-- | Don't log ghc warnings and errors +logToNull :: LogAction +logToNull _ _ _ _ = return () + +-- TODO: Load target by @ModuleLocation@, which may cause updating @DynFlags@
src/HsDev/Tools/HDocs.hs view
@@ -1,60 +1,179 @@-module HsDev.Tools.HDocs (- hdocsy, hdocs, hdocsCabal,- setDocs,- loadDocs,-- hdocsProcess,-- module Control.Monad.Except- ) where--import Control.DeepSeq-import Control.Lens (set, view, over)-import Control.Monad ()-import Control.Monad.Except--import Data.Aeson (decode)-import qualified Data.ByteString.Lazy.Char8 as L (pack)-import Data.Map (Map)-import qualified Data.Map as M-import Data.String (fromString)--import qualified HDocs.Module as HDocs-import qualified HDocs.Haddock as HDocs (readSource, readSources_)--import HsDev.Tools.Base-import HsDev.Symbols---- | Get docs for modules-hdocsy :: [ModuleLocation] -> [String] -> IO [Map String String]-hdocsy mlocs opts = runExceptT (docs' mlocs) >>= return . either (const $ replicate (length mlocs) M.empty) (map $ force . HDocs.formatDocs) where- docs' :: [ModuleLocation] -> ExceptT String IO [HDocs.ModuleDocMap]- docs' ms = liftM (map snd) $ HDocs.readSources_ opts $ map (view moduleFile) ms---- | Get docs for module-hdocs :: ModuleLocation -> [String] -> IO (Map String String)-hdocs mloc opts = runExceptT (docs' mloc) >>= return . either (const M.empty) (force . HDocs.formatDocs) where- docs' :: ModuleLocation -> ExceptT String IO HDocs.ModuleDocMap- docs' (FileModule fpath _) = liftM snd $ HDocs.readSource opts fpath- docs' (InstalledModule _ _ mname) = HDocs.moduleDocs opts mname- docs' _ = throwError $ "Can't get docs for: " ++ show mloc---- | Get all docs-hdocsCabal :: PackageDbStack -> [String] -> ExceptT String IO (Map String (Map String String))-hdocsCabal pdbs opts = liftM (M.map $ force . HDocs.formatDocs) $ HDocs.installedDocs (packageDbStackOpts pdbs ++ opts)---- | Set docs for module-setDocs :: Map String String -> Module -> Module-setDocs d = over moduleDeclarations (map setDoc) where- setDoc decl' = set declarationDocs (M.lookup (view declarationName decl') d') decl'- d' = M.mapKeys fromString . M.map fromString $ d---- | Load docs for module-loadDocs :: [String] -> Module -> IO Module-loadDocs opts m = do- d <- hdocs (view moduleLocation m) opts- return $ setDocs d m--hdocsProcess :: String -> [String] -> IO (Maybe (Map String String))-hdocsProcess mname opts = liftM (decode . L.pack . last . lines) $ runTool_ "hdocs" opts' where- opts' = mname : concat [["-g", opt] | opt <- opts]+{-# LANGUAGE CPP, OverloadedStrings #-} + +module HsDev.Tools.HDocs ( + hdocsy, hdocs, hdocsPackage, hdocsCabal, + setSymbolDocs, setDocs, setModuleDocs, + + hdocsProcess, + + readDocs, readModuleDocs, readProjectTargetDocs, + + hdocsSupported, + + module Control.Monad.Except + ) where + +import Control.Lens +import Control.Monad () +import Control.Monad.Except + +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Text (Text) +#ifdef NODOCS +import qualified System.Log.Simple as Log +#endif + +#ifndef NODOCS +import Control.DeepSeq +import Data.Aeson (decode) +import qualified Data.ByteString.Lazy.Char8 as L (pack) +import Data.String (fromString) +import qualified Data.Text as T + +import qualified HDocs.Module as HDocs +import qualified HDocs.Haddock as HDocs + +import qualified GHC +#endif + +import qualified PackageConfig as P + +import Data.LookupTable +#ifndef NODOCS +import HsDev.Error +import HsDev.Scan.Browse (packageConfigs, readPackage) +import HsDev.Tools.Base +#endif +import HsDev.Symbols +import HsDev.Tools.Ghc.Worker +import System.Directory.Paths + +-- | Get docs for modules +hdocsy :: [ModuleLocation] -> [String] -> GhcM [Map String String] +#ifndef NODOCS +hdocsy mlocs opts = (map $ force . HDocs.formatDocs) <$> docs' mlocs where + docs' :: [ModuleLocation] -> GhcM [HDocs.ModuleDocMap] + docs' ms = do + haddockSession opts + liftGhc $ hsdevLiftWith (ToolError "hdocs") $ + liftM (map snd) $ HDocs.readSourcesGhc opts $ map (view (moduleFile . path)) ms +#else +hdocsy _ _ = notSupported >> return mempty +#endif + +-- | Get docs for module +hdocs :: ModuleLocation -> [String] -> GhcM (Map String String) +#ifndef NODOCS +hdocs mloc opts = (force . HDocs.formatDocs) <$> docs' mloc where + docs' :: ModuleLocation -> GhcM HDocs.ModuleDocMap + docs' mloc' = do + haddockSession opts + liftGhc $ case mloc' of + (FileModule fpath _) -> hsdevLiftWith (ToolError "hdocs") $ liftM snd $ HDocs.readSourceGhc opts (view path fpath) + (InstalledModule _ _ mname) -> do + df <- GHC.getSessionDynFlags + liftIO $ hsdevLiftWith (ToolError "hdocs") $ HDocs.moduleDocsF df (T.unpack mname) + _ -> hsdevError $ ToolError "hdocs" $ "Can't get docs for: " ++ show mloc' +#else +hdocs _ _ = notSupported >> return mempty +#endif + +-- | Get docs for package +hdocsPackage :: P.PackageConfig -> GhcM (Map Text (Map Text Text)) +#ifndef NODOCS +hdocsPackage p = do + ifaces <- + liftIO . hsdevLiftWith (ToolError "hdocs") . + liftM concat . mapM ((`mplus` return []) . HDocs.readInstalledInterfaces) $ + P.haddockInterfaces p + let + idocs = HDocs.installedInterfacesDocs ifaces + iexports = M.fromList $ map (HDocs.exportsDocs idocs) ifaces + docs = M.map HDocs.formatDocs iexports + tdocs = M.map (M.map fromString . M.mapKeys fromString) . M.mapKeys fromString $ docs + return $!! tdocs +#else +hdocsPackage _ = notSupported >> return mempty +#endif + +-- | Get all docs +hdocsCabal :: PackageDbStack -> [String] -> GhcM [(ModulePackage, (Map Text (Map Text Text)))] +#ifndef NODOCS +hdocsCabal pdbs opts = do + haddockSession (packageDbStackOpts pdbs ++ opts) + pkgs <- packageConfigs + forM pkgs $ \pkg -> do + pkgDocs' <- hdocsPackage pkg + return (readPackage pkg, pkgDocs') +#else +hdocsCabal _ _ = notSupported >> return mempty +#endif + +-- | Set docs for module +setSymbolDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text Text -> Symbol -> m Symbol +setSymbolDocs tbl d sym = do + symDocs <- cacheInTableM tbl (symName, symMod) (return $ M.lookup symName d) + return $ set symbolDocs symDocs sym + where + symName = view (symbolId . symbolName) sym + symMod = view (symbolId . symbolModule . moduleName) sym + +-- | Set docs for module symbols +setDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text Text -> Module -> m Module +setDocs tbl d = mapMOf (moduleExports . each) setDoc >=> mapMOf (moduleScope . each . each) setDoc where + setDoc = setSymbolDocs tbl d + +-- | Set docs for modules +setModuleDocs :: MonadIO m => LookupTable (Text, Text) (Maybe Text) -> Map Text (Map Text Text) -> Module -> m Module +setModuleDocs tbl docs m = maybe return (setDocs tbl) (M.lookup (view (moduleId . moduleName) m) docs) $ m + +hdocsProcess :: String -> [String] -> IO (Maybe (Map String String)) +#ifndef NODOCS +hdocsProcess mname opts = liftM (decode . L.pack . last . lines) $ runTool_ "hdocs" opts' where + opts' = mname : concat [["-g", opt] | opt <- opts] +#else +hdocsProcess _ _ = return mempty +#endif + +-- | Read docs for one module +readDocs :: Text -> [String] -> Path -> GhcM (Maybe (Map String String)) +#ifndef NODOCS +readDocs mname opts fpath = do + docs <- liftGhc $ hsdevLift $ HDocs.readSourcesGhc opts [view path fpath] + return $ fmap HDocs.formatDocs $ lookup (T.unpack mname) docs +#else +readDocs _ _ _ = notSupported >> return mempty +#endif + +-- | Read docs for one module +readModuleDocs :: [String] -> Module -> GhcM (Maybe (Map String String)) +#ifndef NODOCS +readModuleDocs opts m = case view (moduleId . moduleLocation) m of + FileModule fpath _ -> withCurrentDirectory (sourceRoot_ (m ^. moduleId) ^. path) $ do + readDocs (m ^. moduleId . moduleName) opts fpath + _ -> hsdevError $ ModuleNotSource (view (moduleId . moduleLocation) m) +#else +readModuleDocs _ _ = notSupported >> return mempty +#endif + +readProjectTargetDocs :: [String] -> Project -> [Path] -> GhcM (Map String (Map String String)) +#ifndef NODOCS +readProjectTargetDocs opts proj fpaths = withCurrentDirectory (proj ^. projectPath . path) $ do + docs <- liftGhc $ hsdevLift $ HDocs.readSourcesGhc opts (fpaths ^.. each . path) + return $ M.map HDocs.formatDocs $ M.fromList docs +#else +readProjectTargetDocs _ _ _ = notSupported >> return mempty +#endif + +#ifdef NODOCS +notSupported :: Log.MonadLog m => m () +notSupported = Log.sendLog Log.Warning "compiled without hdocs support" +#endif + +hdocsSupported :: Bool +#ifndef NODOCS +hdocsSupported = True +#else +hdocsSupported = False +#endif
src/HsDev/Tools/HLint.hs view
@@ -1,97 +1,99 @@-module HsDev.Tools.HLint (- hlint, hlintFile, hlintSource,-- module Control.Monad.Except- ) where--import Control.Arrow-import Control.Lens (over, view, _Just)-import Control.Monad.Except-import Data.Char-import Data.List-import Data.Maybe (mapMaybe)-import Data.Ord-import Language.Haskell.HLint3 (autoSettings, parseModuleEx, applyHints, Idea(..), parseErrorMessage, ParseFlags(..), CppFlags(..))-import Language.Haskell.Exts.SrcLoc-import qualified Language.Haskell.HLint3 as HL (Severity(..))--import System.Directory.Paths (canonicalize)-import HsDev.Symbols.Location-import HsDev.Tools.Base-import HsDev.Util (readFileUtf8, split)--hlint :: FilePath -> Maybe String -> ExceptT String IO [Note OutputMessage]-hlint file msrc = do- file' <- liftIO $ canonicalize file- cts <- maybe (liftIO $ readFileUtf8 file') return msrc- (flags, classify, hint) <- liftIO autoSettings- p <- liftIO $ parseModuleEx (flags { cppFlags = CppSimple }) file' (Just cts)- m <- either (throwError . parseErrorMessage) return p- return $ map (recalcTabs cts 8 . indentIdea cts . fromIdea) $ applyHints classify hint [m]--hlintFile :: FilePath -> ExceptT String IO [Note OutputMessage]-hlintFile f = hlint f Nothing--hlintSource :: FilePath -> String -> ExceptT String IO [Note OutputMessage]-hlintSource f = hlint f . Just--fromIdea :: Idea -> Note OutputMessage-fromIdea idea = Note {- _noteSource = FileModule (srcSpanFilename src) Nothing,- _noteRegion = Region (Position (srcSpanStartLine src) (srcSpanStartColumn src)) (Position (srcSpanEndLine src) (srcSpanEndColumn src)),- _noteLevel = Just $ case ideaSeverity idea of- HL.Warning -> Warning- HL.Error -> Error- _ -> Hint,- _note = OutputMessage {- _message = ideaHint idea,- _messageSuggestion = ideaTo idea } }- where- src = ideaSpan idea--indentIdea :: String -> Note OutputMessage -> Note OutputMessage-indentIdea cts idea = case analyzeIndent cts of- Nothing -> idea- Just i -> over (note . messageSuggestion . _Just) (indent' i) idea- where- indent' i' = intercalate "\n" . indentTail . map (uncurry (++) . first (concat . (`replicate` i') . (`div` 2) . length) . span isSpace) . split (== '\n')- indentTail [] = []- indentTail (h : hs) = h : map (firstIndent ++) hs- firstIndent = takeWhile isSpace firstLine- firstLine = regionStr (Position firstLineNum 1 `region` Position (succ firstLineNum) 1) cts- firstLineNum = view (noteRegion . regionFrom . positionLine) idea---- | Indent in source-data Indent = Spaces Int | Tabs deriving (Eq, Ord)--instance Show Indent where- show (Spaces n) = replicate n ' '- show Tabs = "\t"---- | Analyze source indentation to convert suggestion to same indentation--- Returns one indent-analyzeIndent :: String -> Maybe String-analyzeIndent =- fmap show . selectIndent . map fst . dropUnusual .- sortBy (comparing $ negate . snd) .- map (head &&& length) .- group . sort .- mapMaybe (guessIndent . takeWhile isSpace) . lines- where- selectIndent :: [Indent] -> Maybe Indent- selectIndent [] = Nothing- selectIndent (Tabs : _) = Just Tabs- selectIndent indents = Just $ Spaces $ foldr1 gcd $ mapMaybe spaces indents where- spaces :: Indent -> Maybe Int- spaces Tabs = Nothing- spaces (Spaces n) = Just n- dropUnusual :: [(Indent, Int)] -> [(Indent, Int)]- dropUnusual [] = []- dropUnusual is@((_, freq):_) = takeWhile ((> freq `div` 5) . snd) is---- | Guess indent of one line-guessIndent :: String -> Maybe Indent-guessIndent s- | all (== ' ') s = Just $ Spaces $ length s- | all (== '\t') s = Just Tabs- | otherwise = Nothing+module HsDev.Tools.HLint ( + hlint, + + module Control.Monad.Except + ) where + +import Control.Arrow +import Control.Lens (over, view, _Just) +import Control.Monad.Except +import Data.Char +import Data.List +import Data.Maybe (mapMaybe) +import Data.Text (Text) +import qualified Data.Text as T +import Data.Ord +import Data.String (fromString) +import Language.Haskell.HLint3 (autoSettings, parseModuleEx, applyHints, Idea(..), parseErrorMessage, ParseFlags(..), CppFlags(..)) +import Language.Haskell.Exts.SrcLoc +import qualified Language.Haskell.HLint3 as HL (Severity(..)) + +import System.Directory.Paths +import HsDev.Symbols.Location +import HsDev.Tools.Base +import HsDev.Util (readFileUtf8) + +hlint :: FilePath -> Maybe Text -> ExceptT String IO [Note OutputMessage] +hlint file msrc = do + file' <- liftIO $ canonicalize file + cts <- maybe (liftIO $ readFileUtf8 file') return msrc + (flags, classify, hint) <- liftIO autoSettings + p <- liftIO $ parseModuleEx (flags { cppFlags = CppSimple }) file' (Just $ T.unpack cts) + m <- either (throwError . parseErrorMessage) return p + return $ map (recalcTabs cts 8 . indentIdea cts . fromIdea) $ + filter (not . ignoreIdea) $ + applyHints classify hint [m] + +ignoreIdea :: Idea -> Bool +ignoreIdea idea = ideaSeverity idea == HL.Ignore + +fromIdea :: Idea -> Note OutputMessage +fromIdea idea = Note { + _noteSource = FileModule (fromFilePath $ srcSpanFilename src) Nothing, + _noteRegion = Region (Position (srcSpanStartLine src) (srcSpanStartColumn src)) (Position (srcSpanEndLine src) (srcSpanEndColumn src)), + _noteLevel = Just $ case ideaSeverity idea of + HL.Warning -> Warning + HL.Error -> Error + _ -> Hint, + _note = OutputMessage { + _message = fromString $ ideaHint idea, + _messageSuggestion = fmap fromString $ ideaTo idea } } + where + src = ideaSpan idea + +indentIdea :: Text -> Note OutputMessage -> Note OutputMessage +indentIdea cts idea = case analyzeIndent cts of + Nothing -> idea + Just i -> over (note . messageSuggestion . _Just) (indent' i) idea + where + indent' i' = T.intercalate (fromString "\n") . indentTail . map (uncurry T.append . first ((`T.replicate` i') . (`div` 2) . T.length) . T.span isSpace) . T.split (== '\n') + indentTail [] = [] + indentTail (h : hs) = h : map (firstIndent `T.append`) hs + firstIndent = T.takeWhile isSpace firstLine + firstLine = regionStr (Position firstLineNum 1 `region` Position (succ firstLineNum) 1) cts + firstLineNum = view (noteRegion . regionFrom . positionLine) idea + +-- | Indent in source +data Indent = Spaces Int | Tabs deriving (Eq, Ord) + +instance Show Indent where + show (Spaces n) = replicate n ' ' + show Tabs = "\t" + +-- | Analyze source indentation to convert suggestion to same indentation +-- Returns one indent +analyzeIndent :: Text -> Maybe Text +analyzeIndent = + fmap (fromString . show) . selectIndent . map fst . dropUnusual . + sortBy (comparing $ negate . snd) . + map (head &&& length) . + group . sort . + mapMaybe (guessIndent . T.takeWhile isSpace) . T.lines + where + selectIndent :: [Indent] -> Maybe Indent + selectIndent [] = Nothing + selectIndent (Tabs : _) = Just Tabs + selectIndent indents = Just $ Spaces $ foldr1 gcd $ mapMaybe spaces indents where + spaces :: Indent -> Maybe Int + spaces Tabs = Nothing + spaces (Spaces n) = Just n + dropUnusual :: [(Indent, Int)] -> [(Indent, Int)] + dropUnusual [] = [] + dropUnusual is@((_, freq):_) = takeWhile ((> freq `div` 5) . snd) is + +-- | Guess indent of one line +guessIndent :: Text -> Maybe Indent +guessIndent s + | T.all (== ' ') s = Just $ Spaces $ T.length s + | T.all (== '\t') s = Just Tabs + | otherwise = Nothing
src/HsDev/Tools/Hayoo.hs view
@@ -1,135 +1,139 @@-{-# LANGUAGE OverloadedStrings #-}--module HsDev.Tools.Hayoo (- -- * Types- HayooResult(..), HayooSymbol(..),- hayooAsDeclaration,- -- * Search help online- hayoo,- -- * Utils- untagDescription,-- -- * Reexportss- module Control.Monad.Except- ) where--import Control.Arrow-import Control.Applicative-import Control.Monad.Except--import Data.Aeson-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Either-import Network.HTTP-import Data.String (fromString)--import HsDev.Symbols-import HsDev.Tools.Base (replaceRx)-import HsDev.Util---- | Hayoo response-data HayooResult = HayooResult {- resultMax :: Int,- resultOffset :: Int,- resultCount :: Int,- resultResult :: [HayooSymbol] }- deriving (Eq, Ord, Read, Show)---- | Hayoo symbol-data HayooSymbol = HayooSymbol {- resultUri :: String,- tag :: String,- hayooPackage :: String,- hayooName :: String,- hayooSource :: String,- hayooDescription :: String,- hayooSignature :: String,- hayooModules :: [String],- hayooScore :: Double,- hayooType :: String }- deriving (Eq, Ord, Read, Show)--newtype HayooValue = HayooValue { hayooValue :: Either Value HayooSymbol }--instance FromJSON HayooResult where- parseJSON = withObject "hayoo response" $ \v -> HayooResult <$>- (v .:: "max") <*>- (v .:: "offset") <*>- (v .:: "count") <*>- ((rights . map hayooValue) <$> (v .:: "result"))--instance Symbol HayooSymbol where- symbolName = fromString . hayooName- symbolQualifiedName f = fromString $ case hayooModules f of- [] -> hayooName f- (m:_) -> m ++ "." ++ hayooName f- symbolDocs = Just . fromString . hayooDescription- symbolLocation r = Location (ModuleSource $ Just $ resultUri r) Nothing--instance Documented HayooSymbol where- brief f- | hayooType f == "function" = hayooName f ++ " :: " ++ hayooSignature f- | otherwise = hayooType f ++ " " ++ hayooName f- detailed f = unlines $ defaultDetailed f ++ online where- online = [- "", "Hayoo online documentation", "",- "Package: " ++ hayooPackage f,- "Hackage URL: " ++ resultUri f]--instance FromJSON HayooSymbol where- parseJSON = withObject "symbol" $ \v -> HayooSymbol <$>- (v .:: "resultUri") <*>- (v .:: "tag") <*>- (v .:: "resultPackage") <*>- (v .:: "resultName") <*>- (v .:: "resultSource") <*>- (v .:: "resultDescription") <*>- (v .:: "resultSignature") <*>- (v .:: "resultModules") <*>- (v .:: "resultScore") <*>- (v .:: "resultType")--instance FromJSON HayooValue where- parseJSON v = HayooValue <$> ((Right <$> parseJSON v) <|> pure (Left v))---- | 'HayooFunction' as 'Declaration'-hayooAsDeclaration :: HayooSymbol -> Maybe ModuleDeclaration-hayooAsDeclaration f- | hayooType f `elem` ["function", "type", "newtype", "data", "class"] = Just ModuleDeclaration {- _declarationModuleId = ModuleId {- _moduleIdName = fromString $ head $ hayooModules f,- _moduleIdLocation = ModuleSource (Just $ resultUri f) },- _moduleDeclaration = Declaration {- _declarationName = fromString $ hayooName f,- _declarationDefined = Nothing,- _declarationImported = Nothing,- _declarationDocs = Just (fromString $ addOnline $ untagDescription $ hayooDescription f),- _declarationPosition = Nothing,- _declaration = declInfo } }- | otherwise = Nothing- where- -- Add other info- addOnline d = unlines [- d, "",- "Hayoo online documentation",- "",- "Package: " ++ hayooPackage f,- "Hackage URL: " ++ resultUri f]-- declInfo- | hayooType f == "function" = Function (Just $ fromString $ hayooSignature f) [] Nothing- | hayooType f `elem` ["type", "newtype", "data", "class"] = declarationTypeCtor (hayooType f) $ TypeInfo Nothing [] Nothing []- | otherwise = error "Impossible"---- | Search hayoo-hayoo :: String -> Maybe Int -> ExceptT String IO HayooResult-hayoo q page = do- resp <- ExceptT $ (show +++ rspBody) <$> simpleHTTP (getRequest $ maybe id addPage page $ "http://hayoo.fh-wedel.de/json/?query=" ++ urlEncode q)- ExceptT $ return $ eitherDecode $ L.pack resp- where- addPage :: Int -> String -> String- addPage p s = s ++ "&page=" ++ show p---- | Remove tags in description-untagDescription :: String -> String-untagDescription = replaceRx "</?\\w+[^>]*>" ""+{-# LANGUAGE OverloadedStrings #-} + +module HsDev.Tools.Hayoo ( + -- * Types + HayooResult(..), HayooSymbol(..), + hayooAsSymbol, + -- * Search help online + hayoo, + -- * Utils + untagDescription, + + -- * Reexportss + module Control.Monad.Except + ) where + +import Control.Arrow +import Control.Applicative +import Control.Lens (lens) +import Control.Monad.Except + +import Data.Aeson +import qualified Data.ByteString.Lazy.Char8 as L +import Data.Either +import Data.Maybe (listToMaybe, fromJust) +import Network.HTTP +import Data.String (fromString) +import qualified Data.Text as T (unpack, unlines) + +import HsDev.Symbols +import HsDev.Tools.Base (replaceRx) +import HsDev.Util + +-- | Hayoo response +data HayooResult = HayooResult { + resultMax :: Int, + resultOffset :: Int, + resultCount :: Int, + resultResult :: [HayooSymbol] } + deriving (Eq, Ord, Read, Show) + +-- | Hayoo symbol +data HayooSymbol = HayooSymbol { + resultUri :: String, + tag :: String, + hayooPackage :: String, + hayooName :: String, + hayooSource :: String, + hayooDescription :: String, + hayooSignature :: String, + hayooModules :: [String], + hayooScore :: Double, + hayooType :: String } + deriving (Eq, Ord, Read, Show) + +newtype HayooValue = HayooValue { hayooValue :: Either Value HayooSymbol } + +instance FromJSON HayooResult where + parseJSON = withObject "hayoo response" $ \v -> HayooResult <$> + (v .:: "max") <*> + (v .:: "offset") <*> + (v .:: "count") <*> + ((rights . map hayooValue) <$> (v .:: "result")) + +instance Sourced HayooSymbol where + sourcedName = lens g' s' where + g' = fromString . hayooName + s' sym n = sym { hayooName = T.unpack n } + sourcedModule = lens g' s' where + g' h = ModuleId nm (OtherLocation $ fromString $ resultUri h) where + nm = maybe mempty fromString $ listToMaybe $ hayooModules h + s' h _ = h + sourcedDocs f h = (\d' -> h { hayooDescription = T.unpack d' }) <$> f (fromString $ hayooDescription h) + +instance Documented HayooSymbol where + brief f + | hayooType f == "function" = fromString $ hayooName f ++ " :: " ++ hayooSignature f + | otherwise = fromString $ hayooType f ++ " " ++ hayooName f + detailed f = T.unlines $ defaultDetailed f ++ map fromString online where + online = [ + "", "Hayoo online documentation", "", + "Package: " ++ hayooPackage f, + "Hackage URL: " ++ resultUri f] + +instance FromJSON HayooSymbol where + parseJSON = withObject "symbol" $ \v -> HayooSymbol <$> + (v .:: "resultUri") <*> + (v .:: "tag") <*> + (v .:: "resultPackage") <*> + (v .:: "resultName") <*> + (v .:: "resultSource") <*> + (v .:: "resultDescription") <*> + (v .:: "resultSignature") <*> + (v .:: "resultModules") <*> + (v .:: "resultScore") <*> + (v .:: "resultType") + +instance FromJSON HayooValue where + parseJSON v = HayooValue <$> ((Right <$> parseJSON v) <|> pure (Left v)) + +-- | 'HayooFunction' as 'Symbol' +hayooAsSymbol :: HayooSymbol -> Maybe Symbol +hayooAsSymbol f + | hayooType f `elem` ["function", "type", "newtype", "data", "class"] = Just Symbol { + _symbolId = SymbolId { + _symbolName = fromString $ hayooName f, + _symbolModule = ModuleId { + _moduleName = fromString $ head $ hayooModules f, + _moduleLocation = OtherLocation (fromString $ resultUri f) } }, + _symbolDocs = Just (fromString $ addOnline $ untagDescription $ hayooDescription f), + _symbolPosition = Nothing, + _symbolInfo = info } + | otherwise = Nothing + where + -- Add other info + addOnline d = unlines [ + d, "", + "Hayoo online documentation", + "", + "Package: " ++ hayooPackage f, + "Hackage URL: " ++ resultUri f] + + info + | hayooType f == "function" = Function (Just $ fromString $ hayooSignature f) + | hayooType f `elem` ["type", "newtype", "data", "class"] = (fromJust $ lookup (hayooType f) ctors) [] [] + | otherwise = error "Impossible" + ctors = [("type", Type), ("newtype", NewType), ("data", Data), ("class", Class)] + +-- | Search hayoo +hayoo :: String -> Maybe Int -> ExceptT String IO HayooResult +hayoo q page = do + resp <- ExceptT $ (show +++ rspBody) <$> simpleHTTP (getRequest $ maybe id addPage page $ "http://hayoo.fh-wedel.de/json/?query=" ++ urlEncode q) + ExceptT $ return $ eitherDecode $ L.pack resp + where + addPage :: Int -> String -> String + addPage p s = s ++ "&page=" ++ show p + +-- | Remove tags in description +untagDescription :: String -> String +untagDescription = replaceRx "</?\\w+[^>]*>" ""
+ src/HsDev/Tools/Refact.hs view
@@ -0,0 +1,53 @@+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} + +module HsDev.Tools.Refact ( + Refact(..), refactMessage, refactAction, + refact, update, + + replace, cut, paste, + + fromRegion, fromPosition + ) where + +import Control.Lens hiding ((.=)) +import Data.Aeson +import Data.Text (Text) +import Data.Text.Region hiding (Region(..), update) +import qualified Data.Text.Region as R + +import HsDev.Symbols.Location +import HsDev.Util + +data Refact = Refact { + _refactMessage :: Text, + _refactAction :: Replace Text } + deriving (Eq, Show) + +instance ToJSON Refact where + toJSON (Refact msg cor) = object [ + "message" .= msg, + "action" .= cor] + +instance FromJSON Refact where + parseJSON = withObject "correction" $ \v -> Refact <$> + v .:: "message" <*> + v .:: "action" + +makeLenses ''Refact + +instance Regioned Refact where + regions = refactAction . regions + +refact :: [Refact] -> Text -> Text +refact rs = apply act where + act = Edit (rs ^.. each . refactAction) + +update :: Regioned a => [Refact] -> [a] -> [a] +update rs = map (R.update act) where + act = Edit (rs ^.. each . refactAction) + +fromRegion :: Region -> R.Region +fromRegion (Region f t) = fromPosition f `till` fromPosition t + +fromPosition :: Position -> Point +fromPosition (Position l c) = pt (pred l) (pred c)
src/HsDev/Tools/Types.hs view
@@ -1,99 +1,100 @@-{-# LANGUAGE TemplateHaskell, OverloadedStrings #-}--module HsDev.Tools.Types (- Severity(..),- Note(..), noteSource, noteRegion, noteLevel, note,- OutputMessage(..), message, messageSuggestion, outputMessage- ) where--import Control.DeepSeq (NFData(..))-import Control.Lens (makeLenses)-import Control.Monad-import Data.Aeson hiding (Error)--import System.Directory.Paths-import HsDev.Symbols.Location-import HsDev.Util ((.::), (.::?))---- | Note severity-data Severity = Error | Warning | Hint deriving (Enum, Bounded, Eq, Ord, Read, Show)--instance NFData Severity where- rnf Error = ()- rnf Warning = ()- rnf Hint = ()--instance ToJSON Severity where- toJSON Error = toJSON ("error" :: String)- toJSON Warning = toJSON ("warning" :: String)- toJSON Hint = toJSON ("hint" :: String)--instance FromJSON Severity where- parseJSON v = do- s <- parseJSON v- msum [- guard (s == ("error" :: String)) >> return Error,- guard (s == ("warning" :: String)) >> return Warning,- guard (s == ("hint" :: String)) >> return Hint,- fail $ "Unknown severity: " ++ s]---- | Note over some region-data Note a = Note {- _noteSource :: ModuleLocation,- _noteRegion :: Region,- _noteLevel :: Maybe Severity,- _note :: a }- deriving (Eq, Show)--makeLenses ''Note--instance Functor Note where- fmap f (Note s r l n) = Note s r l (f n)--instance NFData a => NFData (Note a) where- rnf (Note s r l n) = rnf s `seq` rnf r `seq` rnf l `seq` rnf n--instance ToJSON a => ToJSON (Note a) where- toJSON (Note s r l n) = object [- "source" .= s,- "region" .= r,- "level" .= l,- "note" .= n]--instance FromJSON a => FromJSON (Note a) where- parseJSON = withObject "note" $ \v -> Note <$>- v .:: "source" <*>- v .:: "region" <*>- v .::? "level" <*>- v .:: "note"--instance RecalcTabs (Note a) where- recalcTabs cts n' (Note s r l n) = Note s (recalcTabs cts n' r) l n- calcTabs cts n' (Note s r l n) = Note s (calcTabs cts n' r) l n--instance Paths (Note a) where- paths f (Note s r l n) = Note <$> paths f s <*> pure r <*> pure l <*> pure n---- | Output message from some tool (ghc, ghc-mod, hlint) with optional suggestion-data OutputMessage = OutputMessage {- _message :: String,- _messageSuggestion :: Maybe String }- deriving (Eq, Ord, Read, Show)--instance NFData OutputMessage where- rnf (OutputMessage m s) = rnf m `seq` rnf s--instance ToJSON OutputMessage where- toJSON (OutputMessage m s) = object [- "message" .= m,- "suggestion" .= s]--instance FromJSON OutputMessage where- parseJSON = withObject "output-message" $ \v -> OutputMessage <$>- v .:: "message" <*>- v .:: "suggestion"--outputMessage :: String -> OutputMessage-outputMessage msg = OutputMessage msg Nothing--makeLenses ''OutputMessage+{-# LANGUAGE TemplateHaskell, OverloadedStrings #-} + +module HsDev.Tools.Types ( + Severity(..), + Note(..), noteSource, noteRegion, noteLevel, note, + OutputMessage(..), message, messageSuggestion, outputMessage + ) where + +import Control.DeepSeq (NFData(..)) +import Control.Lens (makeLenses) +import Control.Monad +import Data.Aeson hiding (Error) +import Data.Text (Text) + +import System.Directory.Paths +import HsDev.Symbols.Location +import HsDev.Util ((.::), (.::?), noNulls) + +-- | Note severity +data Severity = Error | Warning | Hint deriving (Enum, Bounded, Eq, Ord, Read, Show) + +instance NFData Severity where + rnf Error = () + rnf Warning = () + rnf Hint = () + +instance ToJSON Severity where + toJSON Error = toJSON ("error" :: String) + toJSON Warning = toJSON ("warning" :: String) + toJSON Hint = toJSON ("hint" :: String) + +instance FromJSON Severity where + parseJSON v = do + s <- parseJSON v + msum [ + guard (s == ("error" :: String)) >> return Error, + guard (s == ("warning" :: String)) >> return Warning, + guard (s == ("hint" :: String)) >> return Hint, + fail $ "Unknown severity: " ++ s] + +-- | Note over some region +data Note a = Note { + _noteSource :: ModuleLocation, + _noteRegion :: Region, + _noteLevel :: Maybe Severity, + _note :: a } + deriving (Eq, Show) + +makeLenses ''Note + +instance Functor Note where + fmap f (Note s r l n) = Note s r l (f n) + +instance NFData a => NFData (Note a) where + rnf (Note s r l n) = rnf s `seq` rnf r `seq` rnf l `seq` rnf n + +instance ToJSON a => ToJSON (Note a) where + toJSON (Note s r l n) = object $ noNulls [ + "source" .= s, + "region" .= r, + "level" .= l, + "note" .= n] + +instance FromJSON a => FromJSON (Note a) where + parseJSON = withObject "note" $ \v -> Note <$> + v .:: "source" <*> + v .:: "region" <*> + v .::? "level" <*> + v .:: "note" + +instance RecalcTabs (Note a) where + recalcTabs cts n' (Note s r l n) = Note s (recalcTabs cts n' r) l n + calcTabs cts n' (Note s r l n) = Note s (calcTabs cts n' r) l n + +instance Paths (Note a) where + paths f (Note s r l n) = Note <$> paths f s <*> pure r <*> pure l <*> pure n + +-- | Output message from some tool (ghc, ghc-mod, hlint) with optional suggestion +data OutputMessage = OutputMessage { + _message :: Text, + _messageSuggestion :: Maybe Text } + deriving (Eq, Ord, Read, Show) + +instance NFData OutputMessage where + rnf (OutputMessage m s) = rnf m `seq` rnf s + +instance ToJSON OutputMessage where + toJSON (OutputMessage m s) = object [ + "message" .= m, + "suggestion" .= s] + +instance FromJSON OutputMessage where + parseJSON = withObject "output-message" $ \v -> OutputMessage <$> + v .:: "message" <*> + v .:: "suggestion" + +outputMessage :: Text -> OutputMessage +outputMessage msg = OutputMessage msg Nothing + +makeLenses ''OutputMessage
src/HsDev/Types.hs view
@@ -1,112 +1,124 @@-{-# LANGUAGE OverloadedStrings #-}--module HsDev.Types (- HsDevError(..)- ) where--import Control.Exception-import Control.DeepSeq (NFData(..))-import Data.Aeson-import Data.Aeson.Types (Pair, Parser)-import Data.Typeable-import Text.Format--import HsDev.Symbols.Location---- | hsdev exception type-data HsDevError =- ModuleNotSource ModuleLocation |- BrowseNoModuleInfo String |- FileNotFound FilePath |- ToolNotFound String |- ProjectNotFound String |- PackageNotFound String |- ToolError String String |- NotInspected ModuleLocation |- InspectError String |- InspectCabalError FilePath String |- IOFailed String |- GhcError String |- RequestError String String |- ResponseError String String |- OtherError String- deriving (Typeable)--instance NFData HsDevError where- rnf (ModuleNotSource mloc) = rnf mloc- rnf (BrowseNoModuleInfo m) = rnf m- rnf (FileNotFound f) = rnf f- rnf (ToolNotFound t) = rnf t- rnf (ProjectNotFound p) = rnf p- rnf (PackageNotFound p) = rnf p- rnf (ToolError t e) = rnf t `seq` rnf e- rnf (NotInspected mloc) = rnf mloc- rnf (InspectError e) = rnf e- rnf (InspectCabalError c e) = rnf c `seq` rnf e- rnf (IOFailed e) = rnf e- rnf (GhcError e) = rnf e- rnf (RequestError e r) = rnf e `seq` rnf r- rnf (ResponseError e r) = rnf e `seq` rnf r- rnf (OtherError e) = rnf e--instance Show HsDevError where- show (ModuleNotSource mloc) = format "module is not source: {}" ~~ show mloc- show (BrowseNoModuleInfo m) = format "can't find module info for {}" ~~ m- show (FileNotFound f) = format "file '{}' not found" ~~ f- show (ToolNotFound t) = format "tool '{}' not found" ~~ t- show (ProjectNotFound p) = format "project '{}' not found" ~~ p- show (PackageNotFound p) = format "package '{}' not found" ~~ p- show (ToolError t e) = format "tool '{}' failed: {}" ~~ t ~~ e- show (NotInspected mloc) = "module not inspected: {}" ~~ show mloc- show (InspectError e) = format "failed to inspect: {}" ~~ e- show (InspectCabalError c e) = format "failed to inspect cabal {}: {}" ~~ c ~~ e- show (IOFailed e) = format "io exception: {}" ~~ e- show (GhcError e) = format "ghc exception: {}" ~~ e- show (RequestError e r) = format "request error: {}, request: {}" ~~ e ~~ r- show (ResponseError e r) = format "response error: {}, response: {}" ~~ e ~~ r- show (OtherError e) = e--instance Formattable HsDevError where--jsonErr :: String -> [Pair] -> Value-jsonErr e = object . (("error" .= e) :)--instance ToJSON HsDevError where- toJSON (ModuleNotSource mloc) = jsonErr "module is not source" ["module" .= mloc]- toJSON (BrowseNoModuleInfo m) = jsonErr "no module info" ["module" .= m]- toJSON (FileNotFound f) = jsonErr "file not found" ["file" .= f]- toJSON (ToolNotFound t) = jsonErr "tool not found" ["tool" .= t]- toJSON (ProjectNotFound p) = jsonErr "project not found" ["project" .= p]- toJSON (PackageNotFound p) = jsonErr "package not found" ["package" .= p]- toJSON (ToolError t e) = jsonErr "tool error" ["tool" .= t, "msg" .= e]- toJSON (NotInspected mloc) = jsonErr "module not inspected" ["module" .= mloc]- toJSON (InspectError e) = jsonErr "inspect error" ["msg" .= e]- toJSON (InspectCabalError c e) = jsonErr "inspect cabal error" ["cabal" .= c, "msg" .= e]- toJSON (IOFailed e) = jsonErr "io error" ["msg" .= e]- toJSON (GhcError e) = jsonErr "ghc error" ["msg" .= e]- toJSON (RequestError e r) = jsonErr "request error" ["msg" .= e, "request" .= r]- toJSON (ResponseError e r) = jsonErr "response error" ["msg" .= e, "response" .= r]- toJSON (OtherError e) = jsonErr "other error" ["msg" .= e]--instance FromJSON HsDevError where- parseJSON = withObject "hsdev-error" $ \v -> do- err <- v .: "error" :: Parser String- case err of- "module is not source" -> ModuleNotSource <$> v .: "module"- "no module info" -> BrowseNoModuleInfo <$> v .: "module"- "file not found" -> FileNotFound <$> v .: "file"- "tool not found" -> ToolNotFound <$> v .: "tool"- "project not found" -> ProjectNotFound <$> v .: "project"- "package not found" -> PackageNotFound <$> v .: "package"- "tool error" -> ToolError <$> v .: "tool" <*> v .: "msg"- "module not inspected" -> NotInspected <$> v .: "module"- "inspect error" -> InspectError <$> v .: "msg"- "inspect cabal error" -> InspectCabalError <$> v .: "cabal" <*> v .: "msg"- "io error" -> IOFailed <$> v .: "msg"- "ghc error" -> GhcError <$> v .: "msg"- "request error" -> RequestError <$> v .: "msg" <*> v .: "request"- "response error" -> ResponseError <$> v .: "msg" <*> v .: "response"- "other error" -> OtherError <$> v .: "msg"- _ -> fail "invalid error"--instance Exception HsDevError+{-# LANGUAGE OverloadedStrings #-} + +module HsDev.Types ( + HsDevError(..) + ) where + +import Control.Exception +import Control.DeepSeq (NFData(..)) +import Data.Aeson +import Data.Aeson.Types (Pair, Parser) +import Data.Typeable +import Data.Text (Text) +import Text.Format + +import HsDev.Symbols.Location +import System.Directory.Paths + +-- | hsdev exception type +data HsDevError = + ModuleNotSource ModuleLocation | + BrowseNoModuleInfo String | + FileNotFound Path | + ToolNotFound String | + ProjectNotFound Text | + PackageNotFound Text | + ToolError String String | + NotInspected ModuleLocation | + InspectError String | + InspectCabalError FilePath String | + IOFailed String | + GhcError String | + RequestError String String | + ResponseError String String | + SQLiteError String | + OtherError String | + UnhandledError String + deriving (Typeable) + +instance NFData HsDevError where + rnf (ModuleNotSource mloc) = rnf mloc + rnf (BrowseNoModuleInfo m) = rnf m + rnf (FileNotFound f) = rnf f + rnf (ToolNotFound t) = rnf t + rnf (ProjectNotFound p) = rnf p + rnf (PackageNotFound p) = rnf p + rnf (ToolError t e) = rnf t `seq` rnf e + rnf (NotInspected mloc) = rnf mloc + rnf (InspectError e) = rnf e + rnf (InspectCabalError c e) = rnf c `seq` rnf e + rnf (IOFailed e) = rnf e + rnf (GhcError e) = rnf e + rnf (RequestError e r) = rnf e `seq` rnf r + rnf (ResponseError e r) = rnf e `seq` rnf r + rnf (SQLiteError e) = rnf e + rnf (OtherError e) = rnf e + rnf (UnhandledError e) = rnf e + +instance Show HsDevError where + show (ModuleNotSource mloc) = format "module is not source: {}" ~~ show mloc + show (BrowseNoModuleInfo m) = format "can't find module info for {}" ~~ m + show (FileNotFound f) = format "file '{}' not found" ~~ f + show (ToolNotFound t) = format "tool '{}' not found" ~~ t + show (ProjectNotFound p) = format "project '{}' not found" ~~ p + show (PackageNotFound p) = format "package '{}' not found" ~~ p + show (ToolError t e) = format "tool '{}' failed: {}" ~~ t ~~ e + show (NotInspected mloc) = "module not inspected: {}" ~~ show mloc + show (InspectError e) = format "failed to inspect: {}" ~~ e + show (InspectCabalError c e) = format "failed to inspect cabal {}: {}" ~~ c ~~ e + show (IOFailed e) = format "io exception: {}" ~~ e + show (GhcError e) = format "ghc exception: {}" ~~ e + show (RequestError e r) = format "request error: {}, request: {}" ~~ e ~~ r + show (ResponseError e r) = format "response error: {}, response: {}" ~~ e ~~ r + show (SQLiteError e) = format "sqlite error: {}" ~~ e + show (OtherError e) = e + show (UnhandledError e) = e + +instance Formattable HsDevError where + +jsonErr :: String -> [Pair] -> Value +jsonErr e = object . (("error" .= e) :) + +instance ToJSON HsDevError where + toJSON (ModuleNotSource mloc) = jsonErr "module is not source" ["module" .= mloc] + toJSON (BrowseNoModuleInfo m) = jsonErr "no module info" ["module" .= m] + toJSON (FileNotFound f) = jsonErr "file not found" ["file" .= f] + toJSON (ToolNotFound t) = jsonErr "tool not found" ["tool" .= t] + toJSON (ProjectNotFound p) = jsonErr "project not found" ["project" .= p] + toJSON (PackageNotFound p) = jsonErr "package not found" ["package" .= p] + toJSON (ToolError t e) = jsonErr "tool error" ["tool" .= t, "msg" .= e] + toJSON (NotInspected mloc) = jsonErr "module not inspected" ["module" .= mloc] + toJSON (InspectError e) = jsonErr "inspect error" ["msg" .= e] + toJSON (InspectCabalError c e) = jsonErr "inspect cabal error" ["cabal" .= c, "msg" .= e] + toJSON (IOFailed e) = jsonErr "io error" ["msg" .= e] + toJSON (GhcError e) = jsonErr "ghc error" ["msg" .= e] + toJSON (RequestError e r) = jsonErr "request error" ["msg" .= e, "request" .= r] + toJSON (ResponseError e r) = jsonErr "response error" ["msg" .= e, "response" .= r] + toJSON (SQLiteError e) = jsonErr "sqlite error" ["msg" .= e] + toJSON (OtherError e) = jsonErr "other error" ["msg" .= e] + toJSON (UnhandledError e) = jsonErr "unhandled error" ["msg" .= e] + +instance FromJSON HsDevError where + parseJSON = withObject "hsdev-error" $ \v -> do + err <- v .: "error" :: Parser String + case err of + "module is not source" -> ModuleNotSource <$> v .: "module" + "no module info" -> BrowseNoModuleInfo <$> v .: "module" + "file not found" -> FileNotFound <$> v .: "file" + "tool not found" -> ToolNotFound <$> v .: "tool" + "project not found" -> ProjectNotFound <$> v .: "project" + "package not found" -> PackageNotFound <$> v .: "package" + "tool error" -> ToolError <$> v .: "tool" <*> v .: "msg" + "module not inspected" -> NotInspected <$> v .: "module" + "inspect error" -> InspectError <$> v .: "msg" + "inspect cabal error" -> InspectCabalError <$> v .: "cabal" <*> v .: "msg" + "io error" -> IOFailed <$> v .: "msg" + "ghc error" -> GhcError <$> v .: "msg" + "request error" -> RequestError <$> v .: "msg" <*> v .: "request" + "response error" -> ResponseError <$> v .: "msg" <*> v .: "response" + "sqlite error" -> SQLiteError <$> v .: "msg" + "other error" -> OtherError <$> v .: "msg" + "unhandled error" -> UnhandledError <$> v .: "msg" + _ -> fail "invalid error" + +instance Exception HsDevError
src/HsDev/Util.hs view
@@ -1,340 +1,309 @@-{-# LANGUAGE FlexibleContexts, OverloadedStrings, TemplateHaskell, CPP #-}-{-# OPTIONS_GHC -fno-warn-orphans #-}--module HsDev.Util (- withCurrentDirectory,- directoryContents,- traverseDirectory, searchPath,- isParent,- haskellSource,- cabalFile,- -- * String utils- tab, tabs, trim, split,- -- * Other utils- ordNub, uniqueBy, mapBy,- -- * Helper- (.::), (.::?), (.::?!), objectUnion, jsonUnion, noNulls,- -- * Exceptions- liftException, liftE, liftEIO, tries, triesMap, liftExceptionM, liftIOErrors,- eitherT,- liftThrow,- -- * UTF-8- fromUtf8, toUtf8, readFileUtf8, writeFileUtf8,- -- * IO- hGetLineBS, logException, logIO, ignoreIO, logAsync,- -- * Async- liftAsync,- -- * Command line- FromCmd(..),- cmdJson, withCmd, guardCmd,- withHelp, cmd, parseArgs,- -- * Version stuff- version, cutVersion, sameVersion, strVersion,-- -- * Reexportss- module Control.Monad.Except,- MonadIO(..)- ) where--import Control.Applicative-import Control.Arrow (second, left, (&&&))-import Control.Exception-import Control.Concurrent.Async-import Control.Monad-import Control.Monad.Except-import qualified Control.Monad.Catch as C-import Data.Aeson hiding (Result(..), Error)-import qualified Data.Aeson.Types as A-import Data.Char (isSpace)-import Data.List (isPrefixOf, unfoldr, intercalate)-import qualified Data.Map as M-import Data.Maybe (catMaybes, fromMaybe)-import Data.Monoid ((<>))-import qualified Data.Set as Set-import qualified Data.HashMap.Strict as HM (HashMap, toList, union)-import qualified Data.ByteString.Char8 as B-import Data.ByteString.Lazy (ByteString)-import qualified Data.ByteString.Lazy.Char8 as L-import Data.Text (Text)-import qualified Data.Text.Lazy as T-import qualified Data.Text.Lazy.Encoding as T-import Options.Applicative-import qualified System.Directory as Dir-import System.FilePath-import System.IO-import Text.Read (readMaybe)--#if !MIN_VERSION_directory(1,2,6)-#if mingw32_HOST_OS-import qualified System.Win32 as Win32-import Data.Bits ((.&.))-#else-import qualified System.Posix as Posix-#endif-#endif--import HsDev.Version---- | Run action with current directory set-withCurrentDirectory :: (MonadIO m, C.MonadMask m) => FilePath -> m a -> m a-withCurrentDirectory cur act = C.bracket (liftIO Dir.getCurrentDirectory) (liftIO . Dir.setCurrentDirectory) $- const (liftIO (Dir.setCurrentDirectory cur) >> act)---- | Is directory symbolic link-dirIsSymLink :: FilePath -> IO Bool-#if MIN_VERSION_directory(1,3,0)-dirIsSymLink = Dir.pathIsSymbolicLink-#elif MIN_VERSION_directory(1,2,6)-dirIsSymLink = Dir.isSymbolicLink-#else-dirIsSymLink path = do-#if mingw32_HOST_OS- isReparsePoint <$> Win32.getFileAttributes path- where- fILE_ATTRIBUTE_REPARSE_POINT = 0x400- isReparsePoint attr = attr .&. fILE_ATTRIBUTE_REPARSE_POINT /= 0-#else- Posix.isSymbolicLink <$> Posix.getSymbolicLinkStatus path-#endif-#endif---- | Get directory contents safely: no fail, ignoring symbolic links, also prepends paths with dir name-directoryContents :: FilePath -> IO [FilePath]-directoryContents p = handle ignore $ do- b <- Dir.doesDirectoryExist p- isLink <- dirIsSymLink p- if b && (not isLink)- then liftM (map (p </>) . filter (`notElem` [".", ".."])) (Dir.getDirectoryContents p)- else return []- where- ignore :: SomeException -> IO [FilePath]- ignore _ = return []---- | Collect all file names in directory recursively-traverseDirectory :: FilePath -> IO [FilePath]-traverseDirectory path = handle onError $ do- cts <- directoryContents path- liftM concat $ forM cts $ \c -> do- isDir <- Dir.doesDirectoryExist c- if isDir- then (c :) <$> traverseDirectory c- else return [c]- where- onError :: IOException -> IO [FilePath]- onError _ = return []---- | Search something up-searchPath :: (MonadIO m, MonadPlus m) => FilePath -> (FilePath -> m a) -> m a-searchPath path f = do- path' <- liftIO $ Dir.canonicalizePath path- isDir <- liftIO $ Dir.doesDirectoryExist path'- search' (if isDir then path' else takeDirectory path')- where- search' dir- | isDrive dir = f dir- | otherwise = f dir `mplus` search' (takeDirectory dir)---- | Is one path parent of another-isParent :: FilePath -> FilePath -> Bool-isParent dir file = norm dir `isPrefixOf` norm file where- norm = dropDot . splitDirectories . normalise- dropDot ("." : chs) = chs- dropDot chs = chs---- | Is haskell source?-haskellSource :: FilePath -> Bool-haskellSource f = takeExtension f `elem` [".hs", ".lhs"]---- | Is cabal file?-cabalFile :: FilePath -> Bool-cabalFile f = takeExtension f == ".cabal"---- | Add N tabs to line-tab :: Int -> String -> String-tab n s = replicate n '\t' ++ s---- | Add N tabs to multiline-tabs :: Int -> String -> String-tabs n = unlines . map (tab n) . lines---- | Trim string-trim :: String -> String-trim = p . p where- p = reverse . dropWhile isSpace---- | Split list-split :: (a -> Bool) -> [a] -> [[a]]-split p = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break p)---- | nub is quadratic, https://github.com/nh2/haskell-ordnub/#ordnub-ordNub :: Ord a => [a] -> [a]-ordNub = go Set.empty where- go _ [] = []- go s (x:xs)- | x `Set.member` s = go s xs- | otherwise = x : go (Set.insert x s) xs--uniqueBy :: Ord b => (a -> b) -> [a] -> [a]-uniqueBy f = M.elems . mapBy f--mapBy :: Ord b => (a -> b) -> [a] -> M.Map b a-mapBy f = M.fromList . map (f &&& id)---- | Workaround, sometimes we get HM.lookup "foo" v == Nothing, but lookup "foo" (HM.toList v) == Just smth-(.::) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser a-v .:: name = maybe (fail $ "key " ++ show name ++ " not present") parseJSON $ lookup name $ HM.toList v---- | Returns @Nothing@ when key doesn't exist or value is @Null@-(.::?) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser (Maybe a)-v .::? name = fmap join $ traverse parseJSON $ lookup name $ HM.toList v---- | Same as @.::?@ for list, returns empty list for non-existant key or @Null@ value-(.::?!) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser [a]-v .::?! name = fromMaybe [] <$> (v .::? name)---- | Union two JSON objects-objectUnion :: Value -> Value -> Value-objectUnion (Object l) (Object r) = Object $ HM.union l r-objectUnion (Object l) _ = Object l-objectUnion _ (Object r) = Object r-objectUnion _ _ = Null---- | Union two JSON objects-jsonUnion :: (ToJSON a, ToJSON b) => a -> b -> Value-jsonUnion x y = objectUnion (toJSON x) (toJSON y)---- | No Nulls in JSON object-noNulls :: [A.Pair] -> [A.Pair]-noNulls = filter (not . isNull . snd) where- isNull Null = True- isNull v = v == A.emptyArray || v == A.emptyObject---- | Lift IO exception to ExceptT-liftException :: C.MonadCatch m => m a -> ExceptT String m a-liftException = ExceptT . liftM (left $ \(SomeException e) -> displayException e) . C.try---- | Same as @liftException@-liftE :: C.MonadCatch m => m a -> ExceptT String m a-liftE = liftException---- | @liftE@ for IO-liftEIO :: (C.MonadCatch m, MonadIO m) => IO a -> ExceptT String m a-liftEIO = liftE . liftIO---- | Run actions ignoring errors-tries :: MonadPlus m => [m a] -> m [a]-tries acts = liftM catMaybes $ sequence [liftM Just act `mplus` return Nothing | act <- acts]--triesMap :: MonadPlus m => (a -> m b) -> [a] -> m [b]-triesMap f = tries . map f---- | Lift IO exception to MonadError-liftExceptionM :: (C.MonadCatch m, MonadError String m) => m a -> m a-liftExceptionM act = C.catch act onError where- onError = throwError . (\(SomeException e) -> displayException e)---- | Lift IO exceptions to ExceptT-liftIOErrors :: C.MonadCatch m => ExceptT String m a -> ExceptT String m a-liftIOErrors act = liftException (runExceptT act) >>= either throwError return--eitherT :: MonadError String m => Either String a -> m a-eitherT = either throwError return---- | Throw error as exception-liftThrow :: (Show e, MonadError e m, C.MonadCatch m) => m a -> m a-liftThrow act = catchError act (C.throwM . userError . show)--fromUtf8 :: ByteString -> String-fromUtf8 = T.unpack . T.decodeUtf8--toUtf8 :: String -> ByteString-toUtf8 = T.encodeUtf8 . T.pack---- | Read file in UTF8-readFileUtf8 :: FilePath -> IO String-readFileUtf8 f = withFile f ReadMode $ \h -> do- hSetEncoding h utf8- cts <- hGetContents h- length cts `seq` return cts--writeFileUtf8 :: FilePath -> String -> IO ()-writeFileUtf8 f cts = withFile f WriteMode $ \h -> do- hSetEncoding h utf8- hPutStr h cts--hGetLineBS :: Handle -> IO ByteString-hGetLineBS = fmap L.fromStrict . B.hGetLine--logException :: String -> (String -> IO ()) -> IO () -> IO ()-logException pre out = handle onErr where- onErr :: SomeException -> IO ()- onErr e = out $ pre ++ displayException e--logIO :: C.MonadCatch m => String -> (String -> m ()) -> m () -> m ()-logIO pre out = flip C.catch (onIO out) where- onIO :: (String -> a) -> IOException -> a- onIO out' e = out' $ pre ++ displayException e--logAsync :: (MonadIO m, C.MonadCatch m) => (String -> m ()) -> m () -> m ()-logAsync out = flip C.catch (onAsync out) where- onAsync :: (MonadIO m, C.MonadThrow m) => (String -> m ()) -> AsyncException -> m ()- onAsync out' e = out' (displayException e) >> C.throwM e--ignoreIO :: C.MonadCatch m => m () -> m ()-ignoreIO = C.handle ignore' where- ignore' :: Monad m => IOException -> m ()- ignore' _ = return ()--liftAsync :: (C.MonadThrow m, C.MonadCatch m, MonadIO m) => IO (Async a) -> ExceptT String m a-liftAsync = liftExceptionM . ExceptT . liftIO . liftM (left displayException) . join . liftM waitCatch--class FromCmd a where- cmdP :: Parser a--cmdJson :: String -> [A.Pair] -> Value-cmdJson nm ps = object $ ("command" .= nm) : ps--withCmd :: String -> (Object -> A.Parser a) -> Value -> A.Parser a-withCmd nm fn = withObject ("command " ++ nm) $ \v -> guardCmd nm v *> fn v--guardCmd :: String -> Object -> A.Parser ()-guardCmd nm obj = do- cmdName <- obj .:: "command"- guard (nm == cmdName)---- | Add help command to parser-withHelp :: Parser a -> Parser a-withHelp = (helper' <*>) where- helper' = abortOption ShowHelpText $ long "help" <> short '?' <> help "show help" <> hidden---- | Subcommand-cmd :: String -> String -> Parser a -> Mod CommandFields a-cmd n d p = command n (info (withHelp p) (progDesc d))---- | Parse arguments or return help-parseArgs :: String -> ParserInfo a -> [String] -> Either String a-parseArgs nm p = handle' . execParserPure (prefs mempty) (p { infoParser = withHelp (infoParser p) }) where- handle' :: ParserResult a -> Either String a- handle' (Success r) = Right r- handle' (Failure f) = Left $ fst $ renderFailure f nm- handle' _ = Left "error: completion invoked result"---- instance Log.MonadLog m => Log.MonadLog (ExceptT e m) where--- askLog = lift Log.askLog---- | Get hsdev version as list of integers-version :: Maybe [Int]-version = mapM readMaybe $ split (== '.') $cabalVersion---- | Cut version to contain only significant numbers (currently 3)-cutVersion :: Maybe [Int] -> Maybe [Int]-cutVersion = fmap (take 3)---- | Check if version is the same-sameVersion :: Maybe [Int] -> Maybe [Int] -> Bool-sameVersion l r = fromMaybe False $ liftA2 (==) l r---- | Version to string-strVersion :: Maybe [Int] -> String-strVersion Nothing = "unknown"-strVersion (Just vers) = intercalate "." $ map show vers+{-# LANGUAGE FlexibleContexts, OverloadedStrings, TemplateHaskell, CPP #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} + +module HsDev.Util ( + withCurrentDirectory, + directoryContents, + traverseDirectory, searchPath, + haskellSource, + cabalFile, + -- * String utils + tab, + trim, split, + -- * Other utils + ordNub, uniqueBy, mapBy, + -- * Helper + (.::), (.::?), (.::?!), objectUnion, noNulls, fromJSON', + -- * Exceptions + liftException, liftE, tries, triesMap, liftExceptionM, liftIOErrors, + logAll, + -- * UTF-8 + fromUtf8, toUtf8, readFileUtf8, writeFileUtf8, + -- * IO + hGetLineBS, logIO, ignoreIO, logAsync, + -- * Command line + FromCmd(..), + cmdJson, guardCmd, + withHelp, cmd, parseArgs, + -- * Version stuff + version, + -- * Parse + parseDT, + + -- * Reexportss + module Control.Monad.Except, + MonadIO(..) + ) where + +import Control.Arrow (second, left, (&&&)) +import Control.Exception +import Control.DeepSeq +import Control.Monad +import Control.Monad.Except +import qualified Control.Monad.Catch as C +import Data.Aeson hiding (Result(..), Error) +import qualified Data.Aeson.Types as A +import Data.Char (isSpace) +import Data.List (unfoldr) +import qualified Data.Map.Strict as M +import Data.Maybe (catMaybes, fromMaybe) +import Data.Monoid ((<>)) +import qualified Data.Set as Set +import qualified Data.HashMap.Strict as HM (HashMap, toList, union) +import qualified Data.ByteString.Char8 as B +import Data.ByteString.Lazy (ByteString) +import qualified Data.ByteString.Lazy.Char8 as L +import Data.Text (Text) +import qualified Data.Text.IO as ST +import qualified Data.Text.Lazy as T +import qualified Data.Text.Lazy.Encoding as T +import Distribution.Text (simpleParse) +import qualified Distribution.Text (Text) +import Options.Applicative +import qualified System.Directory as Dir +import System.FilePath +import System.Log.Simple +import System.IO +import Text.Format +import Text.Read (readMaybe) + +#if !MIN_VERSION_directory(1,2,6) +#if mingw32_HOST_OS +import qualified System.Win32 as Win32 +import Data.Bits ((.&.)) +#else +import qualified System.Posix as Posix +#endif +#endif + +import HsDev.Version + +-- | Run action with current directory set +withCurrentDirectory :: (MonadIO m, C.MonadMask m) => FilePath -> m a -> m a +withCurrentDirectory cur act = C.bracket (liftIO Dir.getCurrentDirectory) (liftIO . Dir.setCurrentDirectory) $ + const (liftIO (Dir.setCurrentDirectory cur) >> act) + +-- | Is directory symbolic link +dirIsSymLink :: FilePath -> IO Bool +#if MIN_VERSION_directory(1,3,0) +dirIsSymLink = Dir.pathIsSymbolicLink +#elif MIN_VERSION_directory(1,2,6) +dirIsSymLink = Dir.isSymbolicLink +#else +dirIsSymLink path = do +#if mingw32_HOST_OS + isReparsePoint <$> Win32.getFileAttributes path + where + fILE_ATTRIBUTE_REPARSE_POINT = 0x400 + isReparsePoint attr = attr .&. fILE_ATTRIBUTE_REPARSE_POINT /= 0 +#else + Posix.isSymbolicLink <$> Posix.getSymbolicLinkStatus path +#endif +#endif + +-- | Get directory contents safely: no fail, ignoring symbolic links, also prepends paths with dir name +directoryContents :: FilePath -> IO [FilePath] +directoryContents p = handle ignore $ do + b <- Dir.doesDirectoryExist p + isLink <- dirIsSymLink p + if b && (not isLink) + then liftM (map (p </>) . filter (`notElem` [".", ".."])) (Dir.getDirectoryContents p) + else return [] + where + ignore :: SomeException -> IO [FilePath] + ignore _ = return [] + +-- | Collect all file names in directory recursively +traverseDirectory :: FilePath -> IO [FilePath] +traverseDirectory p = handle onError $ do + cts <- directoryContents p + liftM concat $ forM cts $ \c -> do + isDir <- Dir.doesDirectoryExist c + if isDir + then (c :) <$> traverseDirectory c + else return [c] + where + onError :: IOException -> IO [FilePath] + onError _ = return [] + +-- | Search something up +searchPath :: (MonadIO m, MonadPlus m) => FilePath -> (FilePath -> m a) -> m a +searchPath p f = do + p' <- liftIO $ Dir.canonicalizePath p + isDir <- liftIO $ Dir.doesDirectoryExist p' + search' (if isDir then p' else takeDirectory p') + where + search' dir + | isDrive dir = f dir + | otherwise = f dir `mplus` search' (takeDirectory dir) + +-- | Is haskell source? +haskellSource :: FilePath -> Bool +haskellSource f = takeExtension f `elem` [".hs", ".lhs"] + +-- | Is cabal file? +cabalFile :: FilePath -> Bool +cabalFile f = takeExtension f == ".cabal" + +-- | Add N tabs to line +tab :: Int -> String -> String +tab n s = replicate n '\t' ++ s + +-- | Trim string +trim :: String -> String +trim = p . p where + p = reverse . dropWhile isSpace + +-- | Split list +split :: (a -> Bool) -> [a] -> [[a]] +split p = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break p) + +-- | nub is quadratic, https://github.com/nh2/haskell-ordnub/#ordnub +ordNub :: Ord a => [a] -> [a] +ordNub = go Set.empty where + go _ [] = [] + go s (x:xs) + | x `Set.member` s = go s xs + | otherwise = x : go (Set.insert x s) xs + +uniqueBy :: Ord b => (a -> b) -> [a] -> [a] +uniqueBy f = M.elems . mapBy f + +mapBy :: Ord b => (a -> b) -> [a] -> M.Map b a +mapBy f = M.fromList . map (f &&& id) + +-- | Workaround, sometimes we get HM.lookup "foo" v == Nothing, but lookup "foo" (HM.toList v) == Just smth +(.::) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser a +v .:: name = maybe (fail $ "key " ++ show name ++ " not present") parseJSON $ lookup name $ HM.toList v + +-- | Returns @Nothing@ when key doesn't exist or value is @Null@ +(.::?) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser (Maybe a) +v .::? name = fmap join $ traverse parseJSON $ lookup name $ HM.toList v + +-- | Same as @.::?@ for list, returns empty list for non-existant key or @Null@ value +(.::?!) :: FromJSON a => HM.HashMap Text Value -> Text -> A.Parser [a] +v .::?! name = fromMaybe [] <$> (v .::? name) + +-- | Union two JSON objects +objectUnion :: Value -> Value -> Value +objectUnion (Object l) (Object r) = Object $ HM.union l r +objectUnion (Object l) _ = Object l +objectUnion _ (Object r) = Object r +objectUnion _ _ = Null + +-- | No Nulls in JSON object +noNulls :: [A.Pair] -> [A.Pair] +noNulls = filter (not . isNull . snd) where + isNull Null = True + isNull v = v == A.emptyArray || v == A.emptyObject || v == A.String "" + +-- | Try convert json to value +fromJSON' :: FromJSON a => Value -> Maybe a +fromJSON' v = case fromJSON v of + A.Success r -> Just r + _ -> Nothing + +-- | Lift IO exception to ExceptT +liftException :: C.MonadCatch m => m a -> ExceptT String m a +liftException = ExceptT . liftM (left $ \(SomeException e) -> displayException e) . C.try + +-- | Same as @liftException@ +liftE :: C.MonadCatch m => m a -> ExceptT String m a +liftE = liftException + +-- | Run actions ignoring errors +tries :: MonadPlus m => [m a] -> m [a] +tries acts = liftM catMaybes $ sequence [liftM Just act `mplus` return Nothing | act <- acts] + +triesMap :: MonadPlus m => (a -> m b) -> [a] -> m [b] +triesMap f = tries . map f + +-- | Lift IO exception to MonadError +liftExceptionM :: (C.MonadCatch m, MonadError String m) => m a -> m a +liftExceptionM act = C.catch act onError where + onError = throwError . (\(SomeException e) -> displayException e) + +-- | Lift IO exceptions to ExceptT +liftIOErrors :: C.MonadCatch m => ExceptT String m a -> ExceptT String m a +liftIOErrors act = liftException (runExceptT act) >>= either throwError return + +-- | Log exceptions and ignore +logAll :: (MonadLog m, C.MonadCatch m) => m () -> m () +logAll = C.handleAll logExc' where + logExc' e = sendLog Warning $ "exception: {}" ~~ displayException e + +fromUtf8 :: ByteString -> String +fromUtf8 = T.unpack . T.decodeUtf8 + +toUtf8 :: String -> ByteString +toUtf8 = T.encodeUtf8 . T.pack + +-- | Read file in UTF8 +readFileUtf8 :: FilePath -> IO Text +readFileUtf8 f = withFile f ReadMode $ \h -> do + hSetEncoding h utf8 + cts <- ST.hGetContents h + cts `deepseq` return cts + +writeFileUtf8 :: FilePath -> Text -> IO () +writeFileUtf8 f cts = withFile f WriteMode $ \h -> do + hSetEncoding h utf8 + ST.hPutStr h cts + +hGetLineBS :: Handle -> IO ByteString +hGetLineBS = fmap L.fromStrict . B.hGetLine + +logIO :: C.MonadCatch m => String -> (String -> m ()) -> m () -> m () +logIO pre out = flip C.catch (onIO out) where + onIO :: (String -> a) -> IOException -> a + onIO out' e = out' $ pre ++ displayException e + +logAsync :: (MonadIO m, C.MonadCatch m) => (String -> m ()) -> m () -> m () +logAsync out = flip C.catch (onAsync out) where + onAsync :: (MonadIO m, C.MonadThrow m) => (String -> m ()) -> AsyncException -> m () + onAsync out' e = out' (displayException e) >> C.throwM e + +ignoreIO :: C.MonadCatch m => m () -> m () +ignoreIO = C.handle ignore' where + ignore' :: Monad m => IOException -> m () + ignore' _ = return () + +class FromCmd a where + cmdP :: Parser a + +cmdJson :: String -> [A.Pair] -> Value +cmdJson nm ps = object $ ("command" .= nm) : ps + +guardCmd :: String -> Object -> A.Parser () +guardCmd nm obj = do + cmdName <- obj .:: "command" + guard (nm == cmdName) + +-- | Add help command to parser +withHelp :: Parser a -> Parser a +withHelp = (helper' <*>) where + helper' = abortOption ShowHelpText $ long "help" <> short '?' <> help "show help" <> hidden + +-- | Subcommand +cmd :: String -> String -> Parser a -> Mod CommandFields a +cmd n d p = command n (info (withHelp p) (progDesc d)) + +-- | Parse arguments or return help +parseArgs :: String -> ParserInfo a -> [String] -> Either String a +parseArgs nm p = handle' . execParserPure (prefs mempty) (p { infoParser = withHelp (infoParser p) }) where + handle' :: ParserResult a -> Either String a + handle' (Success r) = Right r + handle' (Failure f) = Left $ fst $ renderFailure f nm + handle' _ = Left "error: completion invoked result" + +-- instance Log.MonadLog m => Log.MonadLog (ExceptT e m) where +-- askLog = lift Log.askLog + +-- | Get hsdev version as list of integers +version :: Maybe [Int] +version = mapM readMaybe $ split (== '.') $cabalVersion + +-- | Parse Distribution.Text +parseDT :: (Monad m, Distribution.Text.Text a) => String -> String -> m a +parseDT typeName v = maybe err return (simpleParse v) where + err = fail $ "Can't parse {}: {}" ~~ typeName ~~ v
src/HsDev/Watcher.hs view
@@ -1,73 +1,73 @@-module HsDev.Watcher (- watchProject, watchModule, watchPackageDb, watchPackageDbStack,- unwatchProject, unwatchModule, unwatchPackageDb,- isSource, isCabal, isConf,-- module System.Directory.Watcher,- module HsDev.Watcher.Types- ) where--import Control.Lens (view)-import Control.Monad (void)-import System.FilePath (takeDirectory, takeExtension, (</>))--import System.Directory.Watcher hiding (Watcher)-import HsDev.Project-import HsDev.Symbols-import HsDev.Watcher.Types-import HsDev.Util---- | Watch for project sources changes-watchProject :: Watcher -> Project -> [String] -> IO ()-watchProject w proj opts = do- mapM_ (\dir -> watchTree w dir isSource (WatchedProject proj opts)) dirs- watchDir w projDir isCabal (WatchedProject proj opts)- where- dirs = map ((projDir </>) . view entity) $ maybe [] sourceDirs $ view projectDescription proj- projDir = view projectPath proj---- | Watch for standalone source-watchModule :: Watcher -> ModuleLocation -> IO ()-watchModule w (FileModule f Nothing) = watchDir w (takeDirectory f) isSource WatchedModule-watchModule w (FileModule _ (Just proj)) = watchProject w proj []-watchModule _ _ = return ()---- | Watch for top of package-db stack-watchPackageDb :: Watcher -> PackageDbStack -> [String] -> IO ()-watchPackageDb w pdbs opts = case topPackageDb pdbs of- GlobalDb -> return () -- TODO: Watch for global package-db- UserDb -> return () -- TODO: Watch for user package-db- (PackageDb pdb) -> watchTree w pdb isConf (WatchedPackageDb pdbs opts)---- | Watch for package-db stack-watchPackageDbStack :: Watcher -> PackageDbStack -> [String] -> IO ()-watchPackageDbStack w pdbs opts = mapM_ (\pdbs' -> watchPackageDb w pdbs' opts) $ packageDbStacks pdbs--unwatchProject :: Watcher -> Project -> IO ()-unwatchProject w proj = do- mapM_ (unwatchTree w) dirs- void $ unwatchDir w projDir- where- dirs = map ((projDir </>) . view entity) $ maybe [] sourceDirs $ view projectDescription proj- projDir = view projectPath proj--unwatchModule :: Watcher -> ModuleLocation -> IO ()-unwatchModule w (FileModule f Nothing) = void $ unwatchDir w (takeDirectory f)-unwatchModule _ (FileModule _ (Just _)) = return ()-unwatchModule _ (InstalledModule _ _ _) = return ()-unwatchModule _ _ = return ()---- | Unwatch package-db-unwatchPackageDb :: Watcher -> PackageDb -> IO ()-unwatchPackageDb _ GlobalDb = return () -- TODO: Unwatch global package-db-unwatchPackageDb _ UserDb = return () -- TODO: Unwatch user package-db-unwatchPackageDb w (PackageDb pdb) = void $ unwatchTree w pdb--isSource :: Event -> Bool-isSource = haskellSource . view eventPath--isCabal :: Event -> Bool-isCabal = cabalFile . view eventPath--isConf :: Event -> Bool-isConf (Event _ f _) = takeExtension f == ".conf"+module HsDev.Watcher ( + watchProject, watchModule, watchPackageDb, watchPackageDbStack, + unwatchProject, unwatchModule, unwatchPackageDb, + isSource, isCabal, isConf, + + module System.Directory.Watcher, + module HsDev.Watcher.Types + ) where + +import Control.Lens (view) +import System.FilePath (takeDirectory, takeExtension, (</>)) + +import System.Directory.Watcher hiding (Watcher) +import System.Directory.Paths +import HsDev.Project +import HsDev.Symbols +import HsDev.Watcher.Types +import HsDev.Util + +-- | Watch for project sources changes +watchProject :: Watcher -> Project -> [String] -> IO () +watchProject w proj opts = do + mapM_ (\dir -> watchTree w dir isSource (WatchedProject proj opts)) dirs + watchDir w projDir isCabal (WatchedProject proj opts) + where + dirs = map ((projDir </>) . view (entity . path)) $ maybe [] sourceDirs $ view projectDescription proj + projDir = view (projectPath . path) proj + +-- | Watch for standalone source +watchModule :: Watcher -> ModuleLocation -> IO () +watchModule w (FileModule f Nothing) = watchDir w (takeDirectory $ view path f) isSource WatchedModule +watchModule w (FileModule _ (Just proj)) = watchProject w proj [] +watchModule _ _ = return () + +-- | Watch for top of package-db stack +watchPackageDb :: Watcher -> PackageDbStack -> [String] -> IO () +watchPackageDb w pdbs opts = case topPackageDb pdbs of + GlobalDb -> return () -- TODO: Watch for global package-db + UserDb -> return () -- TODO: Watch for user package-db + (PackageDb pdb) -> watchTree w (view path pdb) isConf (WatchedPackageDb pdbs opts) + +-- | Watch for package-db stack +watchPackageDbStack :: Watcher -> PackageDbStack -> [String] -> IO () +watchPackageDbStack w pdbs opts = mapM_ (\pdbs' -> watchPackageDb w pdbs' opts) $ packageDbStacks pdbs + +unwatchProject :: Watcher -> Project -> IO () +unwatchProject w proj = do + mapM_ (unwatchTree w) dirs + void $ unwatchDir w projDir + where + dirs = map ((projDir </>) . view (entity . path)) $ maybe [] sourceDirs $ view projectDescription proj + projDir = view (projectPath . path) proj + +unwatchModule :: Watcher -> ModuleLocation -> IO () +unwatchModule w (FileModule f Nothing) = void $ unwatchDir w (takeDirectory $ view path f) +unwatchModule _ (FileModule _ (Just _)) = return () +unwatchModule _ (InstalledModule _ _ _) = return () +unwatchModule _ _ = return () + +-- | Unwatch package-db +unwatchPackageDb :: Watcher -> PackageDb -> IO () +unwatchPackageDb _ GlobalDb = return () -- TODO: Unwatch global package-db +unwatchPackageDb _ UserDb = return () -- TODO: Unwatch user package-db +unwatchPackageDb w (PackageDb pdb) = void $ unwatchTree w (view path pdb) + +isSource :: Event -> Bool +isSource = haskellSource . view eventPath + +isCabal :: Event -> Bool +isCabal = cabalFile . view eventPath + +isConf :: Event -> Bool +isConf (Event _ f _) = takeExtension f == ".conf"
src/System/Directory/Paths.hs view
@@ -1,31 +1,85 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-}--module System.Directory.Paths (- Paths(..),- canonicalize,- absolutise,- relativise- ) where--import Control.Lens-import System.Directory-import System.FilePath---- | Something with paths inside-class Paths a where- paths :: Traversal' a FilePath--instance Paths FilePath where- paths = id---- | Canonicalize all paths-canonicalize :: Paths a => a -> IO a-canonicalize = paths canonicalizePath---- | Absolutise paths-absolutise :: Paths a => FilePath -> a -> a-absolutise parent = over paths (parent </>)---- | Relativise paths-relativise :: Paths a => FilePath -> a -> a-relativise parent = over paths (makeRelative parent)+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances #-} + +module System.Directory.Paths ( + Path, path, fromFilePath, joinPaths, splitPaths, + normPath, subPath, relPathTo, + dirExists, fileExists, takeDir, + isParent, + Paths(..), + canonicalize, + absolutise, + relativise + ) where + +import Control.Lens +import Data.List +import Data.Text (Text, pack) +import Data.Text.Lens (unpacked) +import System.Directory +import System.FilePath + +-- | Takes much less memory than 'FilePath' +type Path = Text + +path :: Lens' Path FilePath +path = unpacked + +fromFilePath :: FilePath -> Path +fromFilePath = pack + +joinPaths :: [Path] -> Path +joinPaths = fromFilePath . joinPath . map (view path) + +splitPaths :: Path -> [Path] +splitPaths = map fromFilePath . splitDirectories . view path + +normPath :: Path -> Path +normPath = over path normalise + +subPath :: Path -> Path -> Path +subPath p ch = fromFilePath (view path p </> view path ch) + +-- | Make path relative +relPathTo :: Path -> Path -> Path +relPathTo base p = fromFilePath $ makeRelative (view path base) (view path p) + +dirExists :: Path -> IO Bool +dirExists = doesDirectoryExist . view path + +fileExists :: Path -> IO Bool +fileExists = doesFileExist . view path + +takeDir :: Path -> Path +takeDir = over path takeDirectory + +-- | Is one path parent of another +isParent :: Path -> Path -> Bool +isParent dir file = norm dir `isPrefixOf` norm file where + norm = dropDot . splitDirectories . normalise . view path + dropDot ("." : chs) = chs + dropDot chs = chs + +-- | Something with paths inside +class Paths a where + paths :: Traversal' a FilePath + +instance Paths FilePath where + paths = id + +instance Paths Path where + paths = unpacked + +-- | Canonicalize all paths +canonicalize :: Paths a => a -> IO a +canonicalize = paths canonicalizePath + +-- | Absolutise paths +absolutise :: Paths a => Path -> a -> a +absolutise parent = over paths addRoot where + addRoot p + | isRelative p = (parent ^. path) </> p + | otherwise = p + +-- | Relativise paths +relativise :: Paths a => Path -> a -> a +relativise parent = over paths (makeRelative (parent ^. path))
src/System/Directory/Watcher.hs view
@@ -1,151 +1,174 @@-{-# LANGUAGE PatternGuards, TemplateHaskell #-}--module System.Directory.Watcher (- EventType(..), Event(..), eventType, eventPath, eventTime,- Watcher(..),- withWatcher,- watchDir, watchDir_, unwatchDir, isWatchingDir,- watchTree, watchTree_, unwatchTree, isWatchingTree,- -- * Working with events- readEvent, events, onEvent- ) where--import Control.Lens (makeLenses)-import Control.Arrow-import Control.Concurrent.MVar-import Control.Concurrent.Chan-import Control.Monad-import Data.Map (Map)-import qualified Data.Map as M-import Data.Maybe (isJust)-import Data.String (fromString)-import Data.Time.Clock.POSIX-import System.FilePath (takeDirectory, isDrive)-import System.Directory-import qualified System.FSNotify as FS---- | Event type-data EventType = Added | Modified | Removed deriving (Eq, Ord, Enum, Bounded, Read, Show)---- | Event-data Event = Event {- _eventType :: EventType,- _eventPath :: FilePath,- _eventTime :: POSIXTime }--makeLenses ''Event---- | Directories watcher-data Watcher a = Watcher {- -- | Map from directory to watch stopper- watcherDirs :: MVar (Map FilePath (Bool, IO ())),- watcherMan :: FS.WatchManager,- watcherChan :: Chan (a, Event) }---- | Create watcher-withWatcher :: (Watcher a -> IO b) -> IO b-withWatcher act = FS.withManager $ \man -> do- ch <- newChan- dirs <- newMVar M.empty- act $ Watcher dirs man ch---- | Watch directory-watchDir :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO ()-watchDir w f p v = do- e <- doesDirectoryExist f- when e $ do- f' <- canonicalizePath f- watching <- isWatchingDir w f'- unless watching $ do- stop <- FS.watchDir- (watcherMan w)- (fromString f')- (p . fromEvent)- (writeChan (watcherChan w) . (,) v . fromEvent)- modifyMVar_ (watcherDirs w) $ return . M.insert f' (False, stop)--watchDir_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO ()-watchDir_ w f p = watchDir w f p ()---- | Unwatch directory, return @False@, if not watched-unwatchDir :: Watcher a -> FilePath -> IO Bool-unwatchDir w f = do- f' <- canonicalizePath f- stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f')- maybe (return ()) snd stop- return $ isJust stop---- | Check if we are watching dir-isWatchingDir :: Watcher a -> FilePath -> IO Bool-isWatchingDir w f = do- f' <- canonicalizePath f- dirs <- readMVar (watcherDirs w)- return $ isWatchingDir' dirs f' || isWatchingParents' dirs f'---- | Watch directory tree-watchTree :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO ()-watchTree w f p v = do- e <- doesDirectoryExist f- when e $ do- f' <- canonicalizePath f- watching <- isWatchingTree w f'- unless watching $ do- stop <- FS.watchTree- (watcherMan w)- (fromString f')- (p . fromEvent)- (writeChan (watcherChan w) . (,) v . fromEvent)- modifyMVar_ (watcherDirs w) $ return . M.insert f' (True, stop)--watchTree_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO ()-watchTree_ w f p = watchTree w f p ()---- | Unwatch directory tree-unwatchTree :: Watcher a -> FilePath -> IO Bool-unwatchTree w f = do- f' <- canonicalizePath f- stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f')- maybe (return ()) snd stop- return $ isJust stop---- | Check if we are watching tree-isWatchingTree :: Watcher a -> FilePath -> IO Bool-isWatchingTree w f = do- f' <- canonicalizePath f- dirs <- readMVar (watcherDirs w)- return $ isWatchingTree' dirs f' || isWatchingParents' dirs f'---- | Read next event-readEvent :: Watcher a -> IO (a, Event)-readEvent = readChan . watcherChan---- | Get lazy list of events-events :: Watcher a -> IO [(a, Event)]-events = getChanContents . watcherChan---- | Process all events-onEvent :: Watcher a -> (a -> Event -> IO ()) -> IO ()-onEvent w act = events w >>= mapM_ (uncurry act)--fromEvent :: FS.Event -> Event-fromEvent e = Event t (FS.eventPath e) (utcTimeToPOSIXSeconds $ FS.eventTime e) where- t = case e of- FS.Added _ _ -> Added- FS.Modified _ _ -> Modified- FS.Removed _ _ -> Removed--isWatchingDir' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool-isWatchingDir' m dir- | Just (_, _) <- M.lookup dir m = True- | isDrive dir = False- | otherwise = isWatchingDir' m (takeDirectory dir)--isWatchingTree' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool-isWatchingTree' m dir- | Just (True, _) <- M.lookup dir m = True- | isDrive dir = False- | otherwise = isWatchingTree' m (takeDirectory dir)--isWatchingParents' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool-isWatchingParents' m dir = or (map (isWatchingTree' m) parents) where- parents = takeWhile (not . isDrive) $ iterate takeDirectory dir+{-# LANGUAGE PatternGuards, TemplateHaskell #-} + +module System.Directory.Watcher ( + EventType(..), Event(..), eventType, eventPath, eventTime, + Watcher(..), + withWatcher, + watchDir, watchDir_, unwatchDir, isWatchingDir, + watchTree, watchTree_, unwatchTree, isWatchingTree, + -- * Working with events + readEvent, eventGroup, onEvents, onEvents_ + ) where + +import Control.Lens (makeLenses) +import Control.Arrow +import Control.Concurrent +import Control.Concurrent.Async +import Control.Concurrent.STM +import Control.Monad +import Data.Map.Strict (Map) +import qualified Data.Map.Strict as M +import Data.Maybe (isJust) +import Data.Ratio ((%)) +import Data.String (fromString) +import Data.Time.Clock (NominalDiffTime) +import Data.Time.Clock.POSIX +import System.FilePath (takeDirectory, isDrive) +import System.Directory +import qualified System.FSNotify as FS + +import HsDev.Util (uniqueBy) + +-- | Event type +data EventType = Added | Modified | Removed deriving (Eq, Ord, Enum, Bounded, Read, Show) + +-- | Event +data Event = Event { + _eventType :: EventType, + _eventPath :: FilePath, + _eventTime :: POSIXTime } + deriving (Eq, Ord, Show) + +makeLenses ''Event + +-- | Directories watcher +data Watcher a = Watcher { + -- | Map from directory to watch stopper + watcherDirs :: MVar (Map FilePath (Bool, IO ())), + watcherMan :: FS.WatchManager, + watcherChan :: Chan (a, Event) } + +-- | Create watcher +withWatcher :: (Watcher a -> IO b) -> IO b +withWatcher act = FS.withManager $ \man -> do + ch <- newChan + dirs <- newMVar M.empty + act $ Watcher dirs man ch + +-- | Watch directory +watchDir :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO () +watchDir w f p v = do + e <- doesDirectoryExist f + when e $ do + f' <- canonicalizePath f + watching <- isWatchingDir w f' + unless watching $ do + stop <- FS.watchDir + (watcherMan w) + (fromString f') + (p . fromEvent) + (writeChan (watcherChan w) . (,) v . fromEvent) + modifyMVar_ (watcherDirs w) $ return . M.insert f' (False, stop) + +watchDir_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO () +watchDir_ w f p = watchDir w f p () + +-- | Unwatch directory, return @False@, if not watched +unwatchDir :: Watcher a -> FilePath -> IO Bool +unwatchDir w f = do + f' <- canonicalizePath f + stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f') + maybe (return ()) snd stop + return $ isJust stop + +-- | Check if we are watching dir +isWatchingDir :: Watcher a -> FilePath -> IO Bool +isWatchingDir w f = do + f' <- canonicalizePath f + dirs <- readMVar (watcherDirs w) + return $ isWatchingDir' dirs f' + +-- | Watch directory tree +watchTree :: Watcher a -> FilePath -> (Event -> Bool) -> a -> IO () +watchTree w f p v = do + e <- doesDirectoryExist f + when e $ do + f' <- canonicalizePath f + watching <- isWatchingTree w f' + unless watching $ do + stop <- FS.watchTree + (watcherMan w) + (fromString f') + (p . fromEvent) + (writeChan (watcherChan w) . (,) v . fromEvent) + modifyMVar_ (watcherDirs w) $ return . M.insert f' (True, stop) + +watchTree_ :: Watcher () -> FilePath -> (Event -> Bool) -> IO () +watchTree_ w f p = watchTree w f p () + +-- | Unwatch directory tree +unwatchTree :: Watcher a -> FilePath -> IO Bool +unwatchTree w f = do + f' <- canonicalizePath f + stop <- modifyMVar (watcherDirs w) $ return . (M.delete f' &&& M.lookup f') + maybe (return ()) snd stop + return $ isJust stop + +-- | Check if we are watching tree +isWatchingTree :: Watcher a -> FilePath -> IO Bool +isWatchingTree w f = do + f' <- canonicalizePath f + dirs <- readMVar (watcherDirs w) + return $ isWatchingTree' dirs f' + +-- | Read next event +readEvent :: Watcher a -> IO (a, Event) +readEvent = readChan . watcherChan + +-- | Get event group +eventGroup :: Watcher a -> NominalDiffTime -> ([(a, Event)] -> IO ()) -> IO () +eventGroup w tm onGroup = do + groupVar <- newTMVarIO [] + syncVar <- newEmptyTMVarIO + _ <- async $ forever $ do + ev <- readChan (watcherChan w) + _ <- atomically $ tryPutTMVar syncVar () + atomically $ do + evs <- takeTMVar groupVar + putTMVar groupVar (ev : evs) + forever $ do + _ <- atomically $ takeTMVar syncVar + threadDelay $ floor (tm * 1e6) + evs' <- atomically $ do + evs <- takeTMVar groupVar + putTMVar groupVar [] + _ <- tryTakeTMVar syncVar + return $ reverse evs + onGroup $ uniqueBy (\(_, ev') -> (_eventType ev', _eventPath ev')) evs' + +-- | Process all events +onEvents :: Watcher a -> NominalDiffTime -> ([(a, Event)] -> IO ()) -> IO () +onEvents = eventGroup + +-- | Process all events +onEvents_ :: Watcher a -> ([(a, Event)] -> IO ()) -> IO () +onEvents_ w = onEvents w (fromRational (1 % 5)) + +fromEvent :: FS.Event -> Event +fromEvent e = Event t (FS.eventPath e) (utcTimeToPOSIXSeconds $ FS.eventTime e) where + t = case e of + FS.Added _ _ -> Added + FS.Modified _ _ -> Modified + FS.Removed _ _ -> Removed + +isWatchingDir' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool +isWatchingDir' m dir + | Just (_, _) <- M.lookup dir m = True + | isDrive dir = False + | otherwise = isWatchingTree' m (takeDirectory dir) + +isWatchingTree' :: Map FilePath (Bool, IO ()) -> FilePath -> Bool +isWatchingTree' m dir + | Just (True, _) <- M.lookup dir m = True + | isDrive dir = False + | otherwise = isWatchingTree' m (takeDirectory dir)
src/System/Win32/FileMapping/NamePool.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE LambdaCase #-} + module System.Win32.FileMapping.NamePool ( Pool(..), createPool, withName @@ -25,7 +27,7 @@ -- | Use free name from pool withName :: Pool -> (String -> IO a) -> IO a withName p = bracket getName freeName where - getName = modifyMVar (poolFreeNames p) $ \names -> case names of + getName = modifyMVar (poolFreeNames p) $ \case [] -> liftM ((,) []) $ poolNewName p - (n:ns) -> return (ns, n) + (n : ns) -> return (ns, n) freeName name = modifyMVar_ (poolFreeNames p) (return . (name:))
src/System/Win32/PowerShell.hs view
@@ -1,214 +1,36 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, GeneralizedNewtypeDeriving, LambdaCase #-}--module System.Win32.PowerShell (- -- * Run PowerShell- ps,- -- * PowerShell functions- startProcess, codepage, outNull,- -- * PowerShell monad- PS(..), seqPS, emit, emit_, (=:), invoke,- -- * Expression- Args(..), CmdLet(..), Expr(..), compile,- raw, name, lit, var, bra, cbra, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter,- -- * Convertible- ToPS(..),- -- * Escape functions- translate, translateArg,- quote, quoteDouble,- escape- ) where--import Prelude hiding (filter)--import Control.Monad-import Control.Monad.Writer-import Data.Char (isAlphaNum)-import Data.List (intercalate)-import qualified Data.List as List (filter)-import Data.Map (Map)-import qualified Data.Map as M--import HsDev.Tools.Base (ToolM, tool_)--ps :: PS a -> ToolM String-ps script = tool_ "powershell" ["-Command", compile $ seqPS script]---- start-process-startProcess :: String -> [String] -> CmdLet-startProcess n as = cmdlet "start-process" [name n, lit $ intercalate ", " (map translateArg as)] [named "WindowStyle" $ lit "Hidden"]---- chcp-codepage :: Int -> CmdLet-codepage n = cmdlet "chcp" [lit n] []---- out-null-outNull :: CmdLet-outNull = cmdlet "out-null" [] []--newtype PS a = PS { unPS :: Writer [Expr] a }- deriving (Applicative, Functor, Monad, MonadWriter [Expr])--seqPS :: PS a -> Expr-seqPS (PS act) = case execWriter act of- [] -> error "No expressions"- es -> foldr1 Sequence es--emit :: Expr -> PS Expr-emit e = PS $ tell [e] >> return e--emit_ :: Expr -> PS ()-emit_ = void . emit--infixr 6 =:-(=:) :: String -> Expr -> PS Expr-n =: expr = emit_ (n .= expr) >> return (var n)---- | Invoke cmdlet-invoke :: CmdLet -> PS ()-invoke = emit_ . Invoke---- | Positional and named args-data Args = Args [Expr] (Map String (Maybe Expr))--instance Monoid Args where- mempty = Args [] M.empty- mappend (Args lp ln) (Args rp rn) = Args (lp ++ rp) (M.union ln rn)---- | Call to cmdlet-data CmdLet = CmdLet {- cmdLet :: Expr,- cmdArgs :: Args }---- | Expression-data Expr =- Emit String |- -- ^ native expression- Literal String |- -- ^ literal- Var String |- -- ^ $name- Bracket Expr |- -- ^ (expr)- Assign [String] [Expr] |- -- ^ $x, $y, ... = expr1, expr2, ...- Invoke CmdLet |- -- ^ cmd args... named...- Lambda [String] ([Expr] -> Expr) |- -- ^ { param($...); expr }- Sequence Expr Expr |- -- ^ expr1; expr2- Pipe Expr CmdLet- -- ^ expr | cmd--compile :: Expr -> String-compile (Emit s) = s-compile (Literal l) = l-compile (Var v) = '$':v-compile (Bracket e) = "(" ++ compile e ++ ")"-compile (Assign vs es) = intercalate ", " (map ('$':) vs) ++ " = " ++ intercalate ", " (map (compile . cbra) es)-compile (Invoke (CmdLet e (Args p ns))) = unwords $ List.filter (not . null) [invoke', p', ns'] where- invoke' = case e of- Var n -> n- _ -> unwords ["&", compile $ cbra e]- p' = unwords $ map (compile . cbra) p- ns' = unwords $ concatMap named' $ M.toList ns where- named' :: (String, Maybe Expr) -> [String]- named' (n, Just v) = ['-':n, compile $ cbra v]- named' (n, Nothing) = ['-':n]--- { param($File); $args[0]; }-compile (Lambda ns body) = unwords ["{", param', body', "}"] where- param' = "param(" ++ intercalate "," ns ++ ");"- body' = compile $ body (map var ns)-compile (Sequence l r) = compile l ++ "; " ++ compile r-compile (Pipe e c) = compile e ++ " | " ++ compile (Invoke c)--raw :: String -> Expr-raw = Emit--name :: String -> Expr-name = Literal--lit :: ToPS a => a -> Expr-lit = Literal . toPS--var :: String -> Expr-var = Var--bra :: Expr -> Expr-bra = Bracket--cbra :: Expr -> Expr-cbra e@(Literal _) = e-cbra v@(Var _) = v-cbra b = bra b--infixr 6 .= -(.=) :: String -> Expr -> Expr-v .= e = Assign [v] [e]--flag :: String -> (String, Maybe Expr)-flag n = (n, Nothing)--named :: String -> Expr -> (String, Maybe Expr)-named n e = (n, Just e)--call :: Expr -> [Expr] -> [(String, Maybe Expr)] -> CmdLet-call f pos named' = CmdLet f $ Args pos (M.fromList named')--cmdlet :: String -> [Expr] -> [(String, Maybe Expr)] -> CmdLet-cmdlet n = call (name n)--lambda :: [String] -> ([Expr] -> Expr) -> Expr-lambda = Lambda--infixr 6 .|-(.|) :: Expr -> CmdLet -> Expr-e .| c = Pipe e c--foreach :: (Expr -> Expr) -> CmdLet-foreach f = cmdlet "%" [f $ var "_"] []--filter :: (Expr -> Expr) -> CmdLet-filter p = cmdlet "?" [p $ var "_"] []--class ToPS a where- toPS :: a -> String--instance ToPS Int where- toPS = show--instance ToPS String where- toPS = translate--instance ToPS Bool where- toPS True = "$true"- toPS False = "$false"--instance {-# OVERLAPPABLE #-} ToPS a => ToPS [a] where- toPS = intercalate ", " . map toPS--translate :: String -> String-translate s- | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s- | otherwise = '"' : snd (foldr escape' (True, "\"") s)- where- escape' '"' (_, s') = (True, '\\' : '"' : s')- escape' '\\' (True, s') = (True, '\\' : '\\' : s')- escape' '\\' (False, s') = (False, '\\' : s')- escape' c (_, s') = (False, c : s')--translateArg :: String -> String-translateArg s- | all isAlphaNum s = s- | otherwise = "'" ++ translate s ++ "'"--quote :: String -> String-quote s = "'" ++ concatMap (\case { '\'' -> "''"; ch -> [ch] }) s ++ "'"--quoteDouble :: String -> String-quoteDouble s = "\"" ++ concatMap (\case { '"' -> "\"\""; ch -> [ch] }) s ++ "\""--escape :: (String -> String) -> String -> String-escape fn s- | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s- | otherwise = fn s+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, LambdaCase #-} + +module System.Win32.PowerShell ( + -- * Escape functions + translate, translateArg, + quote, quoteDouble, + escape + ) where + +import Data.Char (isAlphaNum) + +translate :: String -> String +translate s + | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s + | otherwise = '"' : snd (foldr escape' (True, "\"") s) + where + escape' '"' (_, s') = (True, '\\' : '"' : s') + escape' '\\' (True, s') = (True, '\\' : '\\' : s') + escape' '\\' (False, s') = (False, '\\' : s') + escape' c (_, s') = (False, c : s') + +translateArg :: String -> String +translateArg s + | all isAlphaNum s = s + | otherwise = "'" ++ translate s ++ "'" + +quote :: String -> String +quote s = "'" ++ concatMap (\case { '\'' -> "''"; ch -> [ch] }) s ++ "'" + +quoteDouble :: String -> String +quoteDouble s = "\"" ++ concatMap (\case { '"' -> "\"\""; ch -> [ch] }) s ++ "\"" + +escape :: (String -> String) -> String -> String +escape fn s + | all (\ch -> isAlphaNum ch || ch `elem` "-_") s = s + | otherwise = fn s
tests/Test.hs view
@@ -1,47 +1,100 @@-{-# LANGUAGE OverloadedStrings #-}--module Main (- main- ) where--import Control.Lens-import Control.Exception (displayException)-import Data.Aeson hiding (Error)-import Data.Aeson.Lens-import Data.Default-import HsDev-import Test.Hspec-import System.FilePath-import System.Directory--call :: Server -> Command -> IO (Maybe Value)-call srv c = do- r <- inServer srv def c- case r of- Result v -> return $ Just v- Error e -> do- expectationFailure $ "command result error: " ++ displayException e- return Nothing--exports :: Maybe Value -> [String]-exports v = v ^.. key "exports" . traverseArray . each . key "name" . each . _Just--main :: IO ()-main = hspec $ do- describe "scan project" $ do- dir <- runIO getCurrentDirectory- s <- runIO $ startServer (def { serverSilent = True })- it "should scan project" $ do- _ <- call s $ Scan [dir </> "tests/test-package"] False [] [] [] [] False False- one <- call s $ InfoResolve (dir </> "tests/test-package/ModuleOne.hs") True- when (["test", "forkIO", "untypedFoo"] /= exports one) $- expectationFailure "invalid exports of ModuleOne.hs"- two <- call s $ InfoResolve (dir </> "tests/test-package/ModuleTwo.hs") True- when (["untypedFoo", "twice", "overloadedStrings"] /= exports two) $- expectationFailure "invalid exports of ModuleTwo.hs"- it "should pass extensions when checkings" $ do- checks <- call s (Check [FileSource (dir </> "tests/test-package/ModuleTwo.hs") Nothing] [])- when (("error" :: String) `elem` (checks ^.. traverseArray . key "level" . _Just)) $- expectationFailure "there should be no errors, only warnings"- _ <- runIO $ call s Exit- return ()+{-# LANGUAGE OverloadedStrings #-} + +module Main ( + main + ) where + +import Control.Lens +import Control.Exception (displayException) +import Data.Aeson hiding (Error) +import Data.Aeson.Lens +import Data.Default +import Data.List +import Data.Maybe +import Data.String (fromString) +import qualified Data.Set as S +import Data.Text (unpack) +import System.FilePath +import System.Directory +import Test.Hspec + +import HsDev +import HsDev.Database.SQLite + +send :: Server -> [String] -> IO (Maybe Value) +send srv args = do + r <- sendServer_ srv args + case r of + Result v -> return $ Just v + Error e -> do + expectationFailure $ "command result error: " ++ displayException e + return Nothing + +exports :: Maybe Value -> S.Set String +exports v = mkSet (v ^.. traverseArray . key "exports" . traverseArray . key "id" . key "name" . _Just) + +rgn :: Maybe Value -> Maybe Region +rgn v = Region <$> (pos (v ^. key "from")) <*> (pos (v ^. key "to")) where + pos :: Maybe Value -> Maybe Position + pos v' = Position <$> (v' ^. key "line") <*> (v' ^. key "column") + +mkSet :: [String] -> S.Set String +mkSet = S.fromList + +main :: IO () +main = hspec $ do + describe "scan project" $ do + dir <- runIO getCurrentDirectory + s <- runIO $ startServer_ (def { serverSilent = True }) + it "should load data" $ do + inserts <- liftM lines $ readFile (dir </> "tests/data/base.sql") + inServer s $ mapM_ (execute_ . fromString) inserts + it "should scan project" $ do + void $ send s ["scan", "--project", dir </> "tests/test-package"] + it "should resolve export list" $ do + one <- send s ["module", "ModuleOne", "--exact"] + exports one `shouldBe` mkSet ["test", "newChan", "untypedFoo"] + two <- send s ["module", "ModuleTwo", "--exact"] + exports two `shouldBe` mkSet ["untypedFoo", "twice", "overloadedStrings"] + it "should pass extensions when checks" $ do + checks <- send s ["check", "--file", dir </> "tests/test-package/ModuleTwo.hs"] + (checks ^.. traverseArray . key "level" . _Just) `shouldSatisfy` (("error" :: String) `notElem`) + it "should return types and docs" $ do + test' <- send s ["whois", "test", "--file", dir </> "tests/test-package/ModuleOne.hs"] + let + testType :: Maybe String + testType = test' ^. traverseArray . key "info" . key "type" + testType `shouldBe` Just "IO ()" + it "should infer types" $ do + ts <- send s ["types", "--file", dir </> "tests/test-package/ModuleOne.hs"] + let + untypedFooRgn = Region (Position 14 1) (Position 14 11) + exprs :: [(Region, String)] + exprs = do + note' <- ts ^.. traverseArray + rgn' <- maybeToList $ rgn (note' ^. key "region") + return (rgn', note' ^. key "note" . key "type" . _Just) + lookup untypedFooRgn exprs `shouldBe` Just "a -> a -> a" -- FIXME: Where's Num constraint? + it "should return symbol under location" $ do + _ <- send s ["infer", "--file", dir </> "tests/test-package/ModuleTwo.hs"] + who' <- send s ["whoat", "13", "15", "--file", dir </> "tests/test-package/ModuleTwo.hs"] + let + whoName :: Maybe String + whoName = who' ^. traverseArray . key "id" . key "name" + whoType :: Maybe String + whoType = who' ^. traverseArray . key "info" . key "type" + whoName `shouldBe` Just "f" + whoType `shouldBe` Just "a -> a" + it "should use modified file contents" $ do + modifiedContents <- fmap unpack $ readFileUtf8 $ dir </> "tests/data/ModuleTwo.modified.hs" + void $ send s ["set-file-contents", "--file", dir </> "tests/test-package/ModuleTwo.hs", "--contents", modifiedContents] + -- Call scan to wait until file updated + void $ send s ["scan", "--file", dir </> "tests/test-package/ModuleTwo.hs"] + it "should rescan module with modified contents" $ do + two <- send s ["module", "ModuleTwo", "--exact"] + exports two `shouldBe` mkSet ["untypedFoo", "twice", "overloadedStrings", "useUntypedFoo"] + it "should use modified source in `check` command" $ do + checks <- send s ["check", "--file", dir </> "tests/test-package/ModuleTwo.hs"] + (checks ^.. traverseArray . key "note" . key "message" . _Just) `shouldSatisfy` (any ("Defined but not used" `isPrefixOf`)) + _ <- runIO $ send s ["exit"] + return ()
+ tests/data/ModuleTwo.modified.hs view
@@ -0,0 +1,25 @@+module ModuleTwo ( + untypedFoo, + twice, + overloadedStrings, + useUntypedFoo + ) where + +import ModuleOne (untypedFoo) + +import Data.String + +-- | Apply function twice +twice :: (a -> a) -> a -> a +twice f = f . f + +data MyString = MyString String + +instance IsString MyString where + fromString = MyString + +overloadedStrings :: MyString +overloadedStrings = "Hello" + +useUntypedFoo :: Int -> Int -> Int +useUntypedFoo x y = untypedFoo x (untypedFoo x x)
+ tests/data/base.sql view
@@ -0,0 +1,4 @@+insert into package_dbs values ('global-db', 'base', '4.9.0.0'); +insert into modules values (1, null, null, json('[]'), 'base', '4.9.0.0', 'Control.Concurrent.Chan', null, 'Control.Concurrent.Chan', null, json('[]'), json('{}'), null, null, json('[]')); +insert into symbols values (1, 'newChan', 1, null, null, null, 'function', 'IO (Chan a)', null, null, null, null, null, null, null); +insert into exports values (1, 1);
tests/test-package/ModuleOne.hs view
@@ -1,14 +1,14 @@-module ModuleOne (- test,- forkIO,- untypedFoo- ) where--import Control.Concurrent (forkIO)---- | Some test function-test :: IO ()-test = return ()---- | Some function without type-untypedFoo x y = x + y+module ModuleOne ( + test, + newChan, + untypedFoo + ) where + +import Control.Concurrent.Chan (newChan) + +-- | Some test function +test :: IO () +test = return () + +-- | Some function without type +untypedFoo x y = x + y
− tools/Tool.hs
@@ -1,48 +0,0 @@-{-# LANGUAGE OverloadedStrings #-} - -module Tool ( - -- * Tool - ToolM, toolMain, printExceptT, printResult, runToolClient, - - ClientM, - - toJSON, Value, - module Options.Applicative, - module Data.Monoid, - module HsDev.Util - ) where - -import Control.Monad.Except (ExceptT, runExceptT) -import Control.Monad.IO.Class -import Control.Monad (liftM) -import Data.Aeson -import qualified Data.ByteString.Lazy.Char8 as L (putStrLn) -import Data.Default -import Data.Monoid -import Options.Applicative -import System.Environment -import System.IO - -import HsDev.Client.Commands -import HsDev.Server.Base -import HsDev.Tools.Base (ToolM) -import HsDev.Util (cmd, parseArgs) - --- | Run tool with commands -toolMain :: String -> String -> Parser a -> (a -> IO ()) -> IO () -toolMain name d p act = do - hSetBuffering stdout LineBuffering - hSetEncoding stdout utf8 - hSetEncoding stdin utf8 - res <- liftM (parseArgs name (info p (progDesc d))) getArgs - either putStrLn act res - -printExceptT :: ExceptT String IO () -> IO () -printExceptT act = runExceptT act >>= either putStrLn return - -printResult :: (ToJSON a, MonadIO m) => m a -> m () -printResult act = act >>= liftIO . L.putStrLn . encode - -runToolClient :: ToJSON a => ClientM IO a -> IO () -runToolClient act = runServer silentOpts $ - runClient def act >>= liftIO . L.putStrLn . encode
− tools/hsautofix.hs
@@ -1,87 +0,0 @@-{-# LANGUAGE LambdaCase #-}--module Main (- main- ) where--import Control.Lens (preview)-import Control.Arrow ((***))-import Control.Monad (liftM)-import Control.Monad.IO.Class (liftIO)-import Control.Monad.Except (throwError)-import Data.Aeson hiding (Error)-import Data.List (partition, sort)-import Data.Maybe (mapMaybe)-import System.FilePath (normalise)-import System.Directory (canonicalizePath)-import Text.Read (readMaybe)--import System.Directory.Paths (canonicalize)-import HsDev.Symbols (moduleFile)-import HsDev.Symbols.Location (ModuleLocation(..), regionAt, Position(..))-import HsDev.Tools.Base-import HsDev.Tools.Ghc.Check (recalcNotesTabs)-import HsDev.Tools.AutoFix-import HsDev.Util (toUtf8, liftE, readFileUtf8, writeFileUtf8, ordNub)--import Tool--data FixCmd = ShowCmd Bool | FixCmd [Int] Bool--parseOutputMessages :: String -> [Note OutputMessage]-parseOutputMessages = mapMaybe parseOutputMessage . lines--parseOutputMessage :: String -> Maybe (Note OutputMessage)-parseOutputMessage s = do- groups <- matchRx "^(.+):(\\d+):(\\d+):(\\s*(Warning|Error):)?\\s*(.*)$" s- l <- readMaybe (groups `at` 2)- c <- readMaybe (groups `at` 3)- return Note {- _noteSource = FileModule (normalise (groups `at` 1)) Nothing,- _noteRegion = regionAt (Position l c),- _noteLevel = Just $ if groups 5 == Just "Warning" then Warning else Error,- _note = outputMessage $ nullToNL (groups `at` 6) }---- | Replace NULL with newline-nullToNL :: String -> String-nullToNL = map $ \case- '\0' -> '\n'- ch -> ch--fixP :: Parser FixCmd-fixP = subparser $ mconcat [- cmd "show" "show what can be auto-fixed" (ShowCmd <$> switch (long "json" <> help "output messages in JSON format")),- cmd "fix" "fix selected errors" (FixCmd <$>- many (argument auto (metavar "N" <> help "indices of corrections to apply")) <*>- switch (long "pure" <> help "don't modify files, just return updated corrections"))]--main :: IO ()-main = toolMain "hsautofix" "automatically fix some errors" fixP (printExceptT . printResult . go) where- go (ShowCmd isJson) = do- input <- liftE getContents- msgs <- if isJson- then maybe (throwError "Can't parse messages") return $ decode (toUtf8 input)- else liftIO $ recalcNotesTabs (parseOutputMessages input)- mapM (liftE . canonicalize) $ corrections msgs- go (FixCmd ns pure') = do- input <- liftE getContents- corrs <- maybe (throwError "Can't parse messages") return $ decode (toUtf8 input)- let- check i = i `elem` ns || null ns- (fixCorrs, upCorrs) = (map snd *** map snd) $- partition (check . fst) $ zip [1..] corrs- files <- liftE $ mapM canonicalizePath $ ordNub $ sort $ mapMaybe (preview $ noteSource . moduleFile) corrs- let- doFix :: FilePath -> Maybe String -> ([Note Correction], Maybe String)- doFix file mcts = autoFix fixCorrs' (upCorrs', mcts) where- findCorrs :: FilePath -> [Note Correction] -> [Note Correction]- findCorrs f = filter ((== Just f) . preview (noteSource . moduleFile))- fixCorrs' = findCorrs file fixCorrs- upCorrs' = findCorrs file upCorrs- runFix file- | pure' = return $ fst $ doFix file Nothing- | otherwise = do- (corrs', Just cts') <- liftM (doFix file) $ liftE $ liftM Just $ readFileUtf8 file- liftE $ writeFileUtf8 file cts'- return corrs'- liftM concat $ mapM runFix files
− tools/hscabal.hs
@@ -1,10 +0,0 @@-module Main (- main- ) where--import HsDev.Tools.Cabal--import Tool--main :: IO ()-main = toolMain "hscabal" "cabal tool" (many (strArgument (metavar "package"))) $ printExceptT . printResult . cabalList
− tools/hsclearimports.hs
@@ -1,44 +0,0 @@-module Main (- main- ) where--import Control.Lens (view)-import Control.Exception (finally)-import Control.Monad.Except-import System.Directory--import HsDev.Tools.ClearImports (clearImports)-import HsDev.Symbols (locateSourceDir)-import HsDev.Project (entity)--import Tool--data Opts = Opts {- optsFile :: String,- optsGHC :: [String],- optsHideImportList :: Bool,- optsMaxImportList :: Maybe Int }--opts :: Parser Opts-opts = Opts <$>- strArgument (metavar "file" <> help "file to clear imports in") <*>- many (strOption (long "ghc" <> short 'g' <> metavar "GHC_OPT" <> help "options for GHC")) <*>- switch (long "hide-import-list" <> help "hide import list") <*>- optional (option auto (long "max-import-list" <> metavar "N" <> help "hide long import lists"))--main :: IO ()-main = toolMain "hsclearimports" "clears imports in haskell source" opts $ \opts' -> do- file <- canonicalizePath (optsFile opts')- mroot <- liftM (fmap $ view entity) $ locateSourceDir file- cur <- getCurrentDirectory- flip finally (setCurrentDirectory cur) $ do- maybe (return ()) setCurrentDirectory mroot- void $ runExceptT $ catchError- (clearImports (optsGHC opts') file >>= mapM_ (liftIO . putStrLn . format opts'))- (\e -> liftIO (putStrLn $ "Error: " ++ e))- where- format :: Opts -> (String, String) -> String- format as (imp, lst)- | optsHideImportList as = imp- | maybe False (length lst >) (optsMaxImportList as) = imp- | otherwise = imp ++ " (" ++ lst ++ ")"
− tools/hshayoo.hs
@@ -1,22 +0,0 @@-module Main (- main- ) where--import Data.Maybe--import HsDev.Tools.Hayoo--import Tool--data HayooOpts = HayooOpts String Int Int--hayooOpts :: Parser HayooOpts-hayooOpts = HayooOpts <$>- strArgument (help "hayoo query") <*>- (option auto (long "page" <> short 'p' <> help "page number (0 by default") <|> pure 0) <*>- (option auto (long "pages" <> short 'n' <> help "pages count (1 by default") <|> pure 1)--main :: IO ()-main = toolMain "hshayoo" "hayoo search" hayooOpts $ \(HayooOpts q page pages) -> printExceptT $ printResult $- liftM concat $ forM [page .. page + pred pages] $ \i ->- liftM (mapMaybe hayooAsDeclaration . resultResult) $ hayoo q (Just i)
− tools/hsinspect.hs
@@ -1,52 +0,0 @@-{-# LANGUAGE OverloadedStrings, ViewPatterns #-}--module Main (- main- ) where--import Control.Monad (liftM, (>=>))-import Control.Monad.IO.Class-import System.Directory (canonicalizePath)-import System.FilePath (takeExtension)--import HsDev.Error-import HsDev.Inspect (inspectContents, inspectDocs, getDefines)-import HsDev.PackageDb-import HsDev.Project (readProject)-import HsDev.Scan (scanModule, scanModify)-import HsDev.Symbols.Location (ModuleLocation(..))-import HsDev.Tools.Ghc.Types (inferTypes)-import HsDev.Tools.Ghc.Worker-import HsDev.Tools.Ghc.Session (ghcSession)--import Tool--data Opts = Opts (Maybe String) [String]--opts :: Parser Opts-opts = Opts <$>- optional (strArgument (metavar "what" <> help "depending of what <what> is, inspect installed module, source file (.hs), cabal file (.cabal) or contents, passes as input if no <what> specified")) <*>- many (strOption (metavar "GHC_OPT" <> long "ghc" <> short 'g' <> help "options to pass to GHC"))--main :: IO ()-main = toolMain "hsinspect" "haskell inspect" opts (runToolClient . inspect') where- inspect' :: Opts -> ClientM IO Value- inspect' (Opts Nothing ghcs) = do- cts <- liftIO getContents- defs <- liftIO getDefines- liftM toJSON $ liftIO $ hsdevLift $ inspectContents "stdin" defs ghcs cts- inspect' (Opts (Just fname@(takeExtension -> ".hs")) ghcs) = do- fname' <- liftIO $ canonicalizePath fname- defs <- liftIO getDefines- im <- scanModule defs ghcs (FileModule fname' Nothing) Nothing- ghc <- ghcWorker - let- scanAdditional =- scanModify' (\opts' _ -> liftIO . inspectDocs opts') >=>- scanModify' (\opts' _ m -> liftIO (inWorker ghc (ghcSession opts' >> inferTypes opts' m Nothing)))- toJSON <$> scanAdditional im- inspect' (Opts (Just fcabal@(takeExtension -> ".cabal")) _) = do- fcabal' <- liftIO $ canonicalizePath fcabal- toJSON <$> liftIO (readProject fcabal')- inspect' (Opts (Just mname) ghcs) = toJSON <$> scanModule [] ghcs (InstalledModule UserDb Nothing mname) Nothing- scanModify' f im = scanModify f im <|> return im