hsdev 0.1.2.5 → 0.1.3.0
raw patch · 37 files changed
+1293/−532 lines, 37 filesdep +arraydep +exceptionsdep +monad-controldep −MonadCatchIO-transformersdep −regex-posixdep −regexprdep ~ghc-modPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: array, exceptions, monad-control, regex-pcre-builtin, uniplate
Dependencies removed: MonadCatchIO-transformers, regex-posix, regexpr
Dependency ranges changed: ghc-mod
API changes (from Hackage documentation)
- Control.Concurrent.Worker: ignoreException :: MonadCatchIO m => m () -> m ()
- Control.Concurrent.Worker: sendWork :: Worker a -> a -> IO ()
- Control.Concurrent.Worker: worker :: MonadIO m => (m () -> IO ()) -> ((s -> m ()) -> m ()) -> (s -> a -> m b) -> IO (Worker a)
- Control.Concurrent.Worker: worker_ :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> (a -> m b) -> IO (Worker a)
- HsDev.Database.Update: instance MonadCatchIO m => MonadCatchIO (UpdateDB m)
- HsDev.Tools.Base: match :: String -> String -> Maybe (Int -> Maybe String)
- HsDev.Tools.Ghc.Worker: try :: Ghc a -> Ghc (Either String a)
- HsDev.Tools.Ghc.Worker: waitGhc :: Worker (Ghc ()) -> Ghc a -> ErrorT String IO a
- HsDev.Tools.GhcMod: waitGhcMod :: Worker (GhcModT IO ()) -> GhcModT IO a -> ErrorT String IO a
+ Control.Concurrent.Task: Task :: MVar (Maybe (SomeException -> IO ())) -> MVar (Either SomeException a) -> Task a
+ Control.Concurrent.Task: TaskCancelled :: TaskException
+ Control.Concurrent.Task: TaskKilled :: TaskException
+ Control.Concurrent.Task: data Task a
+ Control.Concurrent.Task: data TaskException
+ Control.Concurrent.Task: forkTask :: IO a -> IO (Task a)
+ Control.Concurrent.Task: instance Bounded TaskException
+ Control.Concurrent.Task: instance Enum TaskException
+ Control.Concurrent.Task: instance Eq TaskException
+ Control.Concurrent.Task: instance Exception TaskException
+ Control.Concurrent.Task: instance Ord TaskException
+ Control.Concurrent.Task: instance Read TaskException
+ Control.Concurrent.Task: instance Show TaskException
+ Control.Concurrent.Task: instance Typeable TaskException
+ Control.Concurrent.Task: runTask :: (MonadCatch m, MonadIO m, MonadIO n) => (m () -> n ()) -> m a -> n (Task a)
+ Control.Concurrent.Task: taskCancel :: Task a -> IO Bool
+ Control.Concurrent.Task: taskCancelled :: Task a -> IO Bool
+ Control.Concurrent.Task: taskDone :: Task a -> IO Bool
+ Control.Concurrent.Task: taskFailed :: Task a -> IO Bool
+ Control.Concurrent.Task: taskKill :: Task a -> IO ()
+ Control.Concurrent.Task: taskResult :: Task a -> MVar (Either SomeException a)
+ Control.Concurrent.Task: taskRunning :: Task a -> IO Bool
+ Control.Concurrent.Task: taskStart :: Task a -> MVar (Maybe (SomeException -> IO ()))
+ Control.Concurrent.Task: taskStarted :: Task a -> IO Bool
+ Control.Concurrent.Task: taskStopped :: Task a -> IO Bool
+ Control.Concurrent.Task: taskWait :: Task a -> IO (Either SomeException a)
+ Control.Concurrent.Task: taskWaitStart :: Task a -> IO Bool
+ Control.Concurrent.Worker: pushTask :: (MonadCatch m, MonadIO m) => Worker m -> m a -> IO (Task a)
+ Control.Concurrent.Worker: sendTask :: (MonadCatch m, MonadIO m) => Worker m -> m a -> IO (Task a)
+ Control.Concurrent.Worker: startWorker :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> IO (Worker m)
+ Control.Concurrent.Worker: workerRestart :: Worker m -> IO Bool
+ Control.Concurrent.Worker: workerTask :: Worker m -> MVar (Task ())
+ HsDev.Cabal: isPackageDb :: FilePath -> Bool
+ HsDev.Database: refineProject :: Database -> Project -> Maybe Project
+ HsDev.Database.Update: databaseCacheWriter :: Settings -> Database -> IO ()
+ HsDev.Database.Update: instance Monad m => MonadWriter [ModuleLocation] (UpdateDB m)
+ HsDev.Database.Update: instance MonadCatch m => MonadCatch (UpdateDB m)
+ HsDev.Database.Update: instance MonadThrow m => MonadThrow (UpdateDB m)
+ HsDev.Project: instance Eq a => Eq (Extensions a)
+ HsDev.Project: instance Read a => Read (Extensions a)
+ HsDev.Project: instance Show a => Show (Extensions a)
+ HsDev.Server.Message: ResultPart :: Value -> ResultPart
+ HsDev.Server.Message: data ResultPart
+ HsDev.Server.Message: instance FromJSON ResultPart
+ HsDev.Server.Message: instance ToJSON ResultPart
+ HsDev.Server.Message: resultPart :: ToJSON a => a -> Notification
+ HsDev.Symbols: ExportModule :: Text -> Export
+ HsDev.Symbols: ExportName :: (Maybe Text) -> Text -> Export
+ HsDev.Symbols: ImportList :: Bool -> [Text] -> ImportList
+ HsDev.Symbols: clearDefinedIn :: Module -> Module
+ HsDev.Symbols: data Export
+ HsDev.Symbols: data ImportList
+ HsDev.Symbols: decl :: Text -> DeclarationInfo -> Declaration
+ HsDev.Symbols: declarationDefined :: Declaration -> Maybe ModuleId
+ HsDev.Symbols: declarationImported :: Declaration -> Maybe [Import]
+ HsDev.Symbols: definedIn :: Declaration -> ModuleId -> Declaration
+ HsDev.Symbols: dropExternals :: Module -> Module
+ HsDev.Symbols: export :: Export -> Text
+ HsDev.Symbols: hidingList :: ImportList -> Bool
+ HsDev.Symbols: importList :: Import -> Maybe ImportList
+ HsDev.Symbols: importName :: Import -> Text
+ HsDev.Symbols: importSpec :: ImportList -> [Text]
+ HsDev.Symbols: import_ :: Text -> Import
+ HsDev.Symbols: importedModulePath :: Text -> FilePath -> Text -> FilePath
+ HsDev.Symbols: instance Eq Export
+ HsDev.Symbols: instance Eq ImportList
+ HsDev.Symbols: instance FromJSON Export
+ HsDev.Symbols: instance FromJSON ImportList
+ HsDev.Symbols: instance NFData Export
+ HsDev.Symbols: instance NFData ImportList
+ HsDev.Symbols: instance Ord Export
+ HsDev.Symbols: instance Ord ImportList
+ HsDev.Symbols: instance Show Export
+ HsDev.Symbols: instance Show ImportList
+ HsDev.Symbols: instance ToJSON Export
+ HsDev.Symbols: instance ToJSON ImportList
+ HsDev.Symbols: passImportList :: ImportList -> Text -> Bool
+ HsDev.Symbols: scopes :: Declaration -> [Maybe Text]
+ HsDev.Symbols: setDefinedIn :: Module -> Module
+ HsDev.Symbols: sortDeclarations :: [Declaration] -> [Declaration]
+ HsDev.Symbols: sourceModuleRoot :: Text -> FilePath -> FilePath
+ HsDev.Symbols: unnamedModuleId :: ModuleLocation -> ModuleId
+ HsDev.Symbols.Location: moduleCabal_ :: ModuleLocation -> Maybe Cabal
+ HsDev.Symbols.Location: moduleProject_ :: ModuleLocation -> Maybe Project
+ HsDev.Symbols.Location: moduleStandalone :: ModuleLocation -> Bool
+ HsDev.Symbols.Location: recalcTabs :: String -> Position -> Position
+ HsDev.Symbols.Resolve: ResolveM :: ReaderT Database (State ResolvedTree) a -> ResolveM a
+ HsDev.Symbols.Resolve: ResolvedModule :: Module -> [Declaration] -> [Declaration] -> ResolvedModule
+ HsDev.Symbols.Resolve: data ResolvedModule
+ HsDev.Symbols.Resolve: exported :: [Declaration] -> Export -> [Declaration]
+ HsDev.Symbols.Resolve: exportsModule :: ResolvedModule -> Module
+ HsDev.Symbols.Resolve: instance Applicative ResolveM
+ HsDev.Symbols.Resolve: instance Functor ResolveM
+ HsDev.Symbols.Resolve: instance Monad ResolveM
+ HsDev.Symbols.Resolve: instance MonadReader Database ResolveM
+ HsDev.Symbols.Resolve: instance MonadState ResolvedTree ResolveM
+ HsDev.Symbols.Resolve: mergeImported :: [Declaration] -> [Declaration]
+ HsDev.Symbols.Resolve: newtype ResolveM a
+ HsDev.Symbols.Resolve: resolve :: (Traversable t, Foldable t) => Database -> t Module -> t ResolvedModule
+ HsDev.Symbols.Resolve: resolveImport :: Module -> Import -> ResolveM [Declaration]
+ HsDev.Symbols.Resolve: resolveModule :: Module -> ResolveM ResolvedModule
+ HsDev.Symbols.Resolve: resolveOne :: Database -> Module -> ResolvedModule
+ HsDev.Symbols.Resolve: resolvedExports :: ResolvedModule -> [Declaration]
+ HsDev.Symbols.Resolve: resolvedModule :: ResolvedModule -> Module
+ HsDev.Symbols.Resolve: resolvedScope :: ResolvedModule -> [Declaration]
+ HsDev.Symbols.Resolve: resolvedTopScope :: ResolvedModule -> [Declaration]
+ HsDev.Symbols.Resolve: runResolveM :: ResolveM a -> ReaderT Database (State ResolvedTree) a
+ HsDev.Symbols.Resolve: scopeModule :: ResolvedModule -> Module
+ HsDev.Symbols.Resolve: type ResolvedTree = Map ModuleId ResolvedModule
+ HsDev.Symbols.Util: cabalOf :: ModuleId -> Maybe Cabal
+ HsDev.Symbols.Util: inDepsOf :: Info -> ModuleId -> Bool
+ HsDev.Tools.Base: matchRx :: String -> String -> Maybe (Int -> Maybe String)
+ HsDev.Tools.Base: replaceRx :: String -> String -> String -> String
+ HsDev.Tools.Base: splitRx :: String -> String -> [String]
+ HsDev.Tools.Ghc.Worker: instance MonadCatch Ghc
+ HsDev.Tools.Ghc.Worker: instance MonadThrow Ghc
+ HsDev.Tools.GhcMod: dispatch :: FilePath -> GhcModT IO a -> ReaderT WorkerMap IO (Task a)
+ HsDev.Tools.GhcMod: flags :: ErrorT String IO [String]
+ HsDev.Tools.GhcMod: instance MonadCatch (GhcModT IO)
+ HsDev.Tools.GhcMod: instance MonadThrow (GhcModT IO)
+ HsDev.Tools.GhcMod: langs :: ErrorT String IO [String]
+ HsDev.Tools.GhcMod: type WorkerMap = MVar (Map FilePath (Worker (GhcModT IO)))
+ HsDev.Util: liftTask :: MonadIO m => IO (Task a) -> ErrorT String m a
+ HsDev.Util: withCurrentDirectory :: FilePath -> IO a -> IO a
- Control.Concurrent.Worker: Worker :: (a -> IO ()) -> Chan a -> Worker a
+ Control.Concurrent.Worker: Worker :: Chan (m ()) -> MVar (Task ()) -> IO Bool -> Worker m
- Control.Concurrent.Worker: data Worker a
+ Control.Concurrent.Worker: data Worker m
- Control.Concurrent.Worker: stopWorker :: Worker a -> IO ()
+ Control.Concurrent.Worker: stopWorker :: Worker m -> IO ()
- Control.Concurrent.Worker: workerChan :: Worker a -> Chan a
+ Control.Concurrent.Worker: workerChan :: Worker m -> Chan (m ())
- HsDev.Commands: completions :: Database -> Cabal -> FilePath -> String -> ErrorT String IO [ModuleDeclaration]
+ HsDev.Commands: completions :: Database -> Cabal -> FilePath -> String -> Bool -> ErrorT String IO [ModuleDeclaration]
- HsDev.Database: filterDB :: (Module -> Bool) -> (Project -> Bool) -> Database -> Database
+ HsDev.Database: filterDB :: (ModuleId -> Bool) -> (Project -> Bool) -> Database -> Database
- HsDev.Database.Update: Settings :: Async Database -> ((FilePath -> ErrorT String IO Structured) -> IO (Maybe Database)) -> (Task -> IO ()) -> [String] -> Settings
+ HsDev.Database.Update: Settings :: Async Database -> ((FilePath -> ErrorT String IO Structured) -> IO (Maybe Database)) -> (Database -> IO ()) -> (Task -> IO ()) -> [String] -> Settings
- HsDev.Database.Update: loadCache :: (MonadIO m, MonadReader Settings m) => (FilePath -> ErrorT String IO Structured) -> m ()
+ HsDev.Database.Update: loadCache :: (MonadIO m, MonadReader Settings m, MonadWriter [ModuleLocation] m) => (FilePath -> ErrorT String IO Structured) -> m ()
- HsDev.Database.Update: scanCabal :: MonadCatchIO m => [String] -> Cabal -> ErrorT String (UpdateDB m) ()
+ HsDev.Database.Update: scanCabal :: (MonadIO m, MonadCatch m) => [String] -> Cabal -> ErrorT String (UpdateDB m) ()
- HsDev.Database.Update: scanDirectory :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
+ HsDev.Database.Update: scanDirectory :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
- HsDev.Database.Update: scanFile :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
+ HsDev.Database.Update: scanFile :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
- HsDev.Database.Update: scanModule :: MonadCatchIO m => [String] -> ModuleLocation -> ErrorT String (UpdateDB m) ()
+ HsDev.Database.Update: scanModule :: (MonadIO m, MonadCatch m) => [String] -> ModuleLocation -> ErrorT String (UpdateDB m) ()
- HsDev.Database.Update: scanModules :: MonadCatchIO m => [String] -> [ModuleToScan] -> ErrorT String (UpdateDB m) ()
+ HsDev.Database.Update: scanModules :: (MonadIO m, MonadCatch m) => [String] -> [ModuleToScan] -> ErrorT String (UpdateDB m) ()
- HsDev.Database.Update: scanProject :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
+ HsDev.Database.Update: scanProject :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
- HsDev.Database.Update: scanProjectFile :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) Project
+ HsDev.Database.Update: scanProjectFile :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) Project
- HsDev.Database.Update: updateDB :: Monad m => Settings -> ErrorT String (UpdateDB m) () -> m ()
+ HsDev.Database.Update: updateDB :: MonadIO m => Settings -> ErrorT String (UpdateDB m) () -> m ()
- HsDev.Database.Update: updater :: (MonadIO m, MonadReader Settings m) => m Database -> m ()
+ HsDev.Database.Update: updater :: (MonadIO m, MonadReader Settings m, MonadWriter [ModuleLocation] m) => m Database -> m ()
- HsDev.Server.Types: CommandOptions :: Async Database -> (Database -> IO ()) -> ((FilePath -> ErrorT String IO Structured) -> IO (Maybe Database)) -> FilePath -> (String -> IO ()) -> (([String] -> IO ()) -> IO ()) -> IO () -> Worker (Ghc ()) -> Worker (FilePath, GhcModT IO ()) -> (Notification -> IO ()) -> IO () -> IO () -> IO () -> CommandOptions
+ HsDev.Server.Types: CommandOptions :: Async Database -> (Database -> IO ()) -> ((FilePath -> ErrorT String IO Structured) -> IO (Maybe Database)) -> FilePath -> (String -> IO ()) -> (([String] -> IO ()) -> IO ()) -> IO () -> Worker Ghc -> Worker (ReaderT WorkerMap IO) -> (Notification -> IO ()) -> IO () -> IO () -> IO () -> CommandOptions
- HsDev.Server.Types: commandGhc :: CommandOptions -> Worker (Ghc ())
+ HsDev.Server.Types: commandGhc :: CommandOptions -> Worker Ghc
- HsDev.Server.Types: commandGhcMod :: CommandOptions -> Worker (FilePath, GhcModT IO ())
+ HsDev.Server.Types: commandGhcMod :: CommandOptions -> Worker (ReaderT WorkerMap IO)
- HsDev.Symbols: Declaration :: String -> Maybe String -> Maybe Position -> DeclarationInfo -> Declaration
+ HsDev.Symbols: Declaration :: Text -> Maybe ModuleId -> Maybe [Import] -> Maybe Text -> Maybe Position -> DeclarationInfo -> Declaration
- HsDev.Symbols: Function :: Maybe String -> [Declaration] -> DeclarationInfo
+ HsDev.Symbols: Function :: Maybe Text -> [Declaration] -> DeclarationInfo
- HsDev.Symbols: Import :: String -> Bool -> Maybe String -> Maybe Position -> Import
+ HsDev.Symbols: Import :: Text -> Bool -> Maybe Text -> Maybe ImportList -> Maybe Position -> Import
- HsDev.Symbols: Module :: String -> Maybe String -> ModuleLocation -> [String] -> [Import] -> Map String Declaration -> Module
+ HsDev.Symbols: Module :: Text -> Maybe Text -> ModuleLocation -> Maybe [Export] -> [Import] -> [Declaration] -> Module
- HsDev.Symbols: ModuleId :: String -> ModuleLocation -> ModuleId
+ HsDev.Symbols: ModuleId :: Text -> ModuleLocation -> ModuleId
- HsDev.Symbols: TypeInfo :: Maybe String -> [String] -> Maybe String -> TypeInfo
+ HsDev.Symbols: TypeInfo :: Maybe Text -> [Text] -> Maybe Text -> TypeInfo
- HsDev.Symbols: declarationDocs :: Declaration -> Maybe String
+ HsDev.Symbols: declarationDocs :: Declaration -> Maybe Text
- HsDev.Symbols: declarationInfo :: DeclarationInfo -> Either (Maybe String, [Declaration]) TypeInfo
+ HsDev.Symbols: declarationInfo :: DeclarationInfo -> Either (Maybe Text, [Declaration]) TypeInfo
- HsDev.Symbols: declarationName :: Declaration -> String
+ HsDev.Symbols: declarationName :: Declaration -> Text
- HsDev.Symbols: functionType :: DeclarationInfo -> Maybe String
+ HsDev.Symbols: functionType :: DeclarationInfo -> Maybe Text
- HsDev.Symbols: importAs :: Import -> Maybe String
+ HsDev.Symbols: importAs :: Import -> Maybe Text
- HsDev.Symbols: importModuleName :: Import -> String
+ HsDev.Symbols: importModuleName :: Import -> Text
- HsDev.Symbols: importQualifier :: Maybe String -> Import -> Bool
+ HsDev.Symbols: importQualifier :: Maybe Text -> Import -> Bool
- HsDev.Symbols: moduleDeclarations :: Module -> Map String Declaration
+ HsDev.Symbols: moduleDeclarations :: Module -> [Declaration]
- HsDev.Symbols: moduleDocs :: Module -> Maybe String
+ HsDev.Symbols: moduleDocs :: Module -> Maybe Text
- HsDev.Symbols: moduleExports :: Module -> [String]
+ HsDev.Symbols: moduleExports :: Module -> Maybe [Export]
- HsDev.Symbols: moduleIdName :: ModuleId -> String
+ HsDev.Symbols: moduleIdName :: ModuleId -> Text
- HsDev.Symbols: moduleLocalDeclarations :: Module -> Map String Declaration
+ HsDev.Symbols: moduleLocalDeclarations :: Module -> [Declaration]
- HsDev.Symbols: moduleName :: Module -> String
+ HsDev.Symbols: moduleName :: Module -> Text
- HsDev.Symbols: qualifiedName :: ModuleId -> Declaration -> String
+ HsDev.Symbols: qualifiedName :: ModuleId -> Declaration -> Text
- HsDev.Symbols: symbolDocs :: Symbol a => a -> Maybe String
+ HsDev.Symbols: symbolDocs :: Symbol a => a -> Maybe Text
- HsDev.Symbols: symbolName :: Symbol a => a -> String
+ HsDev.Symbols: symbolName :: Symbol a => a -> Text
- HsDev.Symbols: symbolQualifiedName :: Symbol a => a -> String
+ HsDev.Symbols: symbolQualifiedName :: Symbol a => a -> Text
- HsDev.Symbols: typeInfoArgs :: TypeInfo -> [String]
+ HsDev.Symbols: typeInfoArgs :: TypeInfo -> [Text]
- HsDev.Symbols: typeInfoContext :: TypeInfo -> Maybe String
+ HsDev.Symbols: typeInfoContext :: TypeInfo -> Maybe Text
- HsDev.Symbols: typeInfoDefinition :: TypeInfo -> Maybe String
+ HsDev.Symbols: typeInfoDefinition :: TypeInfo -> Maybe Text
- HsDev.Symbols: unalias :: Module -> String -> [String]
+ HsDev.Symbols: unalias :: Module -> Text -> [Text]
- HsDev.Symbols.Class: symbolDocs :: Symbol a => a -> Maybe String
+ HsDev.Symbols.Class: symbolDocs :: Symbol a => a -> Maybe Text
- HsDev.Symbols.Class: symbolName :: Symbol a => a -> String
+ HsDev.Symbols.Class: symbolName :: Symbol a => a -> Text
- HsDev.Symbols.Class: symbolQualifiedName :: Symbol a => a -> String
+ HsDev.Symbols.Class: symbolQualifiedName :: Symbol a => a -> Text
- HsDev.Tools.Ghc.Worker: ghcWorker :: IO (Worker (Ghc ()))
+ HsDev.Tools.Ghc.Worker: ghcWorker :: IO (Worker Ghc)
- HsDev.Tools.GhcMod: ghcModMultiWorker :: IO (Worker (FilePath, GhcModT IO ()))
+ HsDev.Tools.GhcMod: ghcModMultiWorker :: IO (Worker (ReaderT WorkerMap IO))
- HsDev.Tools.GhcMod: ghcModWorker :: Either Project Cabal -> IO (Worker (GhcModT IO ()))
+ HsDev.Tools.GhcMod: ghcModWorker :: Either Project Cabal -> IO (Worker (GhcModT IO))
- HsDev.Tools.GhcMod: info :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> String -> GhcModT IO Declaration
+ HsDev.Tools.GhcMod: info :: [String] -> Cabal -> FilePath -> String -> GhcModT IO Declaration
- HsDev.Tools.GhcMod: runGhcMod :: (IOish m, MonadCatchIO m) => Options -> GhcModT m a -> ErrorT String m a
+ HsDev.Tools.GhcMod: runGhcMod :: (IOish m, MonadCatch m) => Options -> GhcModT m a -> ErrorT String m a
- HsDev.Tools.GhcMod: typeOf :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> Int -> Int -> GhcModT IO [TypedRegion]
+ HsDev.Tools.GhcMod: typeOf :: [String] -> Cabal -> FilePath -> Int -> Int -> GhcModT IO [TypedRegion]
- HsDev.Tools.GhcMod: waitMultiGhcMod :: Worker (FilePath, GhcModT IO ()) -> FilePath -> GhcModT IO a -> ErrorT String IO a
+ HsDev.Tools.GhcMod: waitMultiGhcMod :: Worker (ReaderT WorkerMap IO) -> FilePath -> GhcModT IO a -> ErrorT String IO a
- HsDev.Tools.GhcMod.InferType: inferType :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> Declaration -> GhcModT IO Declaration
+ HsDev.Tools.GhcMod.InferType: inferType :: [String] -> Cabal -> FilePath -> Declaration -> GhcModT IO Declaration
- HsDev.Util: liftException :: MonadCatchIO m => m a -> ErrorT String m a
+ HsDev.Util: liftException :: MonadCatch m => m a -> ErrorT String m a
- HsDev.Util: liftExceptionM :: (MonadCatchIO m, Error e, MonadError e m) => m a -> m a
+ HsDev.Util: liftExceptionM :: (MonadCatch m, Error e, MonadError e m) => m a -> m a
- HsDev.Util: liftIOErrors :: MonadCatchIO m => ErrorT String m a -> ErrorT String m a
+ HsDev.Util: liftIOErrors :: MonadCatch m => ErrorT String m a -> ErrorT String m a
Files
- hsdev.cabal +18/−13
- src/Control/Concurrent/Task.hs +80/−0
- src/Control/Concurrent/Worker.hs +32/−25
- src/HsDev/Cabal.hs +16/−16
- src/HsDev/Cache/Structured.hs +3/−3
- src/HsDev/Client/Commands.hs +96/−34
- src/HsDev/Commands.hs +44/−34
- src/HsDev/Database.hs +10/−6
- src/HsDev/Database/Update.hs +44/−22
- src/HsDev/Inspect.hs +84/−39
- src/HsDev/Project.hs +2/−1
- src/HsDev/Scan.hs +3/−1
- src/HsDev/Scan/Browse.hs +26/−19
- src/HsDev/Server/Commands.hs +85/−38
- src/HsDev/Server/Message.hs +15/−3
- src/HsDev/Server/Types.hs +4/−3
- src/HsDev/Symbols.hs +195/−52
- src/HsDev/Symbols/Class.hs +5/−3
- src/HsDev/Symbols/Documented.hs +3/−1
- src/HsDev/Symbols/Location.hs +31/−4
- src/HsDev/Symbols/Resolve.hs +153/−0
- src/HsDev/Symbols/Util.hs +18/−4
- src/HsDev/Tools/Base.hs +22/−6
- src/HsDev/Tools/Ghc/Prelude.hs +14/−3
- src/HsDev/Tools/Ghc/Worker.hs +10/−16
- src/HsDev/Tools/GhcMod.hs +129/−84
- src/HsDev/Tools/GhcMod/InferType.hs +21/−12
- src/HsDev/Tools/HDocs.hs +6/−4
- src/HsDev/Tools/Hayoo.hs +12/−9
- src/HsDev/Util.hs +21/−9
- src/System/Console/Cmd.hs +9/−3
- src/System/Win32/FileMapping/Memory.hs +18/−14
- src/System/Win32/PowerShell.hs +36/−14
- src/Text/Format.hs +10/−8
- tools/Tool.hs +1/−4
- tools/hsdev.hs +5/−9
- tools/hsinspect.hs +12/−16
hsdev.cabal view
@@ -2,7 +2,7 @@ -- see http://haskell.org/cabal/users-guide/ name: hsdev -version: 0.1.2.5 +version: 0.1.3.0 synopsis: Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references etc. description: Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc. @@ -22,6 +22,7 @@ exposed-modules: Control.Apply.Util Control.Concurrent.FiniteChan + Control.Concurrent.Task Control.Concurrent.Worker Control.Concurrent.Util Data.Async @@ -49,6 +50,7 @@ HsDev.Symbols.Class HsDev.Symbols.Location HsDev.Symbols.Documented + HsDev.Symbols.Resolve HsDev.Symbols.Util HsDev.Tools.Base HsDev.Tools.Cabal @@ -81,34 +83,37 @@ base >= 4.7 && < 5, aeson >= 0.7.0, aeson-pretty >= 0.7.0, + array >= 0.5.0, + attoparsec >= 0.11.0, bytestring >= 0.10.0, Cabal >= 1.18.0, containers >= 0.5.0, deepseq >= 1.3.0, directory >= 1.2.0, + exceptions >= 0.6.0, filepath >= 1.3.0, ghc >= 7.8.1, - ghc-mod >= 5.1.0.0 && < 5.2.0.0, + ghc-mod >= 5.2.0.0, ghc-paths >= 0.1.0, haddock-api >= 2.15.0, haskell-src-exts >= 1.16.0, hdocs >= 0.4.0, HTTP >= 4000.2.0, - MonadCatchIO-transformers >= 0.3.0, + -- ghc-mod workaround + monad-control >= 0.3 && < 1.0, monad-loops >= 0.4, mtl >= 2.1.0, network >= 2.4.0, - transformers >= 0.3.0, process >= 1.2.0, - regexpr >= 0.5.0, - regex-posix >= 0.95, + regex-pcre-builtin >= 0.94, scientific >= 0.3, - time >= 1.4.0, - attoparsec >= 0.11.0, template-haskell, + text >= 1.1.0, + time >= 1.4.0, + transformers >= 0.3.0, + uniplate >= 1.6.0, unordered-containers >= 0.2.0, - vector >= 0.10.0, - text >= 1.1.0 + vector >= 0.10.0 executable hsdev main-is: hsdev.hs @@ -124,13 +129,12 @@ containers >= 0.5.0, deepseq >= 1.3.0, directory >= 1.2.0, + exceptions >= 0.6.0, filepath >= 1.3.0, monad-loops >= 0.4.0, - MonadCatchIO-transformers >= 0.3.0, mtl >= 2.1.0, network >= 2.4.0, process >= 1.2.0, - unordered-containers >= 0.2.0, text >= 1.1.0, transformers >= 0.3.0, unordered-containers >= 0.2.0 @@ -148,6 +152,7 @@ aeson-pretty >= 0.7.0, bytestring >= 0.10.0, containers >= 0.5.0, + filepath >= 1.3.0, mtl >= 2.1.0, text >= 1.1.0, transformers >= 0.3.0, @@ -164,10 +169,10 @@ hsdev, aeson >= 0.7.0, aeson-pretty >= 0.7.0, + containers >= 0.5.0, directory >= 1.2.0, ghc >= 7.8.1, haskell-src-exts >= 1.16.0, - containers >= 0.5.0, mtl >= 2.1.0, text >= 1.1.0, unordered-containers >= 0.2.0
+ src/Control/Concurrent/Task.hs view
@@ -0,0 +1,80 @@+{-# LANGUAGE DeriveDataTypeable #-} + +module Control.Concurrent.Task ( + Task(..), TaskException(..), + taskStarted, taskRunning, taskStopped, taskDone, taskFailed, taskCancelled, + taskWaitStart, taskWait, taskKill, taskCancel, + runTask, forkTask + ) where + +import Control.Applicative +import Control.Concurrent +import Control.Monad +import Control.Monad.IO.Class +import Control.Monad.Catch +import Data.Either +import Data.Maybe +import Data.Traversable +import Data.Typeable + +-- | Task result +data Task a = Task { + taskStart :: MVar (Maybe (SomeException -> IO ())), + taskResult :: MVar (Either SomeException a) } + +data TaskException = TaskCancelled | TaskKilled deriving (Eq, Ord, Enum, Bounded, Read, Show, Typeable) + +instance Exception TaskException + +taskStarted :: Task a -> IO Bool +taskStarted = fmap (maybe False isJust) . tryReadMVar . taskStart + +taskRunning :: Task a -> IO Bool +taskRunning t = (&&) <$> taskStarted t <*> (not <$> taskStopped t) + +taskStopped :: Task a -> IO Bool +taskStopped = fmap not . isEmptyMVar . taskResult + +taskDone :: Task a -> IO Bool +taskDone = fmap (maybe False isRight) . tryReadMVar . taskResult + +taskFailed :: Task a -> IO Bool +taskFailed = fmap (maybe False isLeft) . tryReadMVar . taskResult + +taskCancelled :: Task a -> IO Bool +taskCancelled = fmap (maybe False isNothing) . tryReadMVar . taskStart + +-- | Wait until task starts or be cancelled, returns True if started +taskWaitStart :: Task a -> IO Bool +taskWaitStart = (`withMVar` (return . isJust)) . taskStart + +-- | Wait for task +taskWait :: Task a -> IO (Either SomeException a) +taskWait = takeMVar . taskResult + +-- | Kill task +taskKill :: Task a -> IO () +taskKill = + tryTakeMVar . taskStart >=> + void . traverse ($ toException TaskKilled) . join + +-- | Cancel task if it is not started yet +taskCancel :: Task a -> IO Bool +taskCancel t = do + aborted <- tryPutMVar (taskStart t) Nothing + when aborted $ void $ tryPutMVar (taskResult t) (Left $ toException TaskCancelled) + return aborted + +runTask :: (MonadCatch m, MonadIO m, MonadIO n) => (m () -> n ()) -> m a -> n (Task a) +runTask f act = do + throwVar <- liftIO newEmptyMVar + resultVar <- liftIO newEmptyMVar + f $ handle (liftIO . putMVar resultVar . Left) $ do + th <- liftIO myThreadId + ok <- liftIO $ tryPutMVar throwVar (Just $ throwTo th) + when ok $ act >>= liftIO . putMVar resultVar . Right + return $ Task throwVar resultVar + +-- | Run task in separate thread +forkTask :: IO a -> IO (Task a) +forkTask = runTask (void . forkIO)
src/Control/Concurrent/Worker.hs view
@@ -1,38 +1,45 @@ module Control.Concurrent.Worker ( Worker(..), - worker_, worker, + startWorker, + sendTask, pushTask, stopWorker, - ignoreException + + module Control.Concurrent.Task ) where -import Control.Exception (SomeException) -import Control.Monad (void) +import Control.Concurrent.MVar +import Control.Monad (void, when) import Control.Monad.IO.Class -import Control.Monad.CatchIO +import Control.Monad.Catch -import Control.Concurrent (forkIO) import Control.Concurrent.FiniteChan - -data Worker a = Worker { - sendWork :: a -> IO (), - workerChan :: Chan a } +import Control.Concurrent.Task -worker_ :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> (a -> m b) -> IO (Worker a) -worker_ run initialize work = worker run initialize' work' where - initialize' f = initialize $ f () - work' _ = work +data Worker m = Worker { + workerChan :: Chan (m ()), + workerTask :: MVar (Task ()), + workerRestart :: IO Bool } -worker :: MonadIO m => (m () -> IO ()) -> ((s -> m ()) -> m ()) -> (s -> a -> m b) -> IO (Worker a) -worker run initialize work = do +startWorker :: MonadIO m => (m () -> IO ()) -> (m () -> m ()) -> IO (Worker m) +startWorker run initialize = do ch <- newChan - void $ forkIO $ run $ initialize $ \s -> - liftIO (readChan ch) >>= mapM_ (work s) - return $ Worker (putChan ch) ch + taskVar <- newEmptyMVar + let + start = forkTask $ run $ initialize processWork + processWork = liftIO (getChan ch) >>= maybe (return ()) (>> processWork) + start >>= putMVar taskVar + let + restart = do + f <- readMVar taskVar >>= taskFailed + when f (start >>= void . swapMVar taskVar) + return f + return $ Worker ch taskVar restart -stopWorker :: Worker a -> IO () -stopWorker = closeChan . workerChan +sendTask :: (MonadCatch m, MonadIO m) => Worker m -> m a -> IO (Task a) +sendTask w = runTask $ putChan (workerChan w) -ignoreException :: MonadCatchIO m => m () -> m () -ignoreException act = catch act onError where - onError :: MonadCatchIO m => SomeException -> m () - onError _ = return () +pushTask :: (MonadCatch m, MonadIO m) => Worker m -> m a -> IO (Task a) +pushTask w act = workerRestart w >> sendTask w act + +stopWorker :: Worker m -> IO () +stopWorker = closeChan . workerChan
src/HsDev/Cabal.hs view
@@ -1,8 +1,8 @@-{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings, MultiWayIf #-} module HsDev.Cabal ( Cabal(..), sandbox, - findPackageDb, locateSandbox, getSandbox, + isPackageDb, findPackageDb, locateSandbox, getSandbox, cabalOpt ) where @@ -43,25 +43,25 @@ sandboxP = withObject "sandbox" sandboxPath where sandboxPath obj = fmap Sandbox $ obj .: "sandbox" --- | Find -package-db path for sandbox +-- | Is -package-db file +isPackageDb :: FilePath -> Bool +isPackageDb p = cabalDev p || cabalSandbox p where + cabalDev dir = "packages-" `isPrefixOf` dir && ".conf" `isSuffixOf` dir + cabalSandbox dir = "-packages.conf.d" `isSuffixOf` dir + +-- | Find -package-db path for sandbox directory or package-db file itself findPackageDb :: FilePath -> IO (Maybe FilePath) findPackageDb sand = do sand' <- canonicalizePath sand isDir <- doesDirectoryExist sand' - if isDir then locateHere sand' else locateParent (takeDirectory sand') - where - locateHere path = do - cts <- filter (not . null . takeBaseName) <$> getDirectoryContents path - return $ fmap (path </>) $ find cabalDev cts <|> find cabalSandbox cts - locateParent dir = do - cts <- filter (not . null . takeBaseName) <$> getDirectoryContents dir - case find cabalDev cts <|> find cabalSandbox cts of - Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir) - Just packagef -> return $ Just (dir </> packagef) - cabalDev p = "packages-" `isPrefixOf` p && ".conf" `isSuffixOf` p - cabalSandbox p = "-packages.conf.d" `isSuffixOf` p + if + | isDir && isPackageDb sand' -> return $ Just sand' + | isDir -> do + cts <- filter (not . null . takeBaseName) <$> getDirectoryContents sand' + return $ fmap (sand' </>) $ find isPackageDb cts + | otherwise -> return Nothing --- | Create sandbox by parent directory +-- | Create sandbox by directory or package-db file locateSandbox :: FilePath -> ErrorT String IO Cabal locateSandbox p = liftIO (findPackageDb p) >>= maybe (throwError $ "Can't locate package-db in sandbox: " ++ p)
src/HsDev/Cache/Structured.hs view
@@ -4,6 +4,7 @@ ) where import Control.Applicative +import Control.DeepSeq import Control.Exception import Control.Monad.Error import qualified Data.Map as M (assocs) @@ -32,8 +33,7 @@ files' <- either (const zero) id <$> handle wrapIO (Cache.load (dir </> Cache.standaloneCache)) - Cache.dump (dir </> Cache.standaloneCache) $ - files' `mappend` structuredFiles db + files' `deepseq` Cache.dump (dir </> Cache.standaloneCache) (files' `mappend` structuredFiles db) where wrapIO :: SomeException -> IO (Either String Database) wrapIO = return . Left . show @@ -71,5 +71,5 @@ dat <- loadData (dir </> Cache.standaloneCache) ErrorT $ return $ structured [] [] $ filterDB inFiles (const False) dat where - inFiles = maybe False (`elem` fs') . moduleSource . moduleLocation + inFiles = maybe False (`elem` fs') . moduleSource . moduleIdLocation fs' = map normalise fs
src/HsDev/Client/Commands.hs view
@@ -8,6 +8,7 @@ import Control.Arrow import Control.Monad import Control.Monad.Error +import Control.Monad.Catch (try, SomeException(..)) import Control.Monad.Trans.Maybe import Data.Aeson hiding (Result, Error) import Data.Aeson.Encode.Pretty @@ -16,7 +17,9 @@ import Data.Maybe import Data.Monoid import qualified Data.Map as M +import Data.String (fromString) import Data.Text (unpack) +import qualified Data.Text as T (isInfixOf, isPrefixOf) import Data.Traversable (traverse) import System.Directory import System.FilePath @@ -27,6 +30,7 @@ import HsDev.Database import HsDev.Project import HsDev.Symbols +import HsDev.Symbols.Resolve (resolveOne, scopeModule, exportsModule) import HsDev.Symbols.Util import HsDev.Util import HsDev.Scan @@ -66,7 +70,7 @@ ghcOpts]) "rescan sources" rescan', - cmd' "remove" [] (sandboxes ++ [ + cmdList' "remove" [] (sandboxes ++ [ projectArg `desc` "module project", fileArg `desc` "module source file", moduleArg, @@ -75,16 +79,16 @@ "remove modules info" remove', -- Context free commands - cmd' "modules" [] (sandboxes ++ [ + cmdList' "modules" [] (sandboxes ++ [ manyReq $ projectArg `desc` "projects to list modules from", noLastArg, manyReq packageArg, sourced, standaloned]) "list modules" listModules', - cmd' "packages" [] [] "list packages" listPackages', - cmd' "projects" [] [] "list projects" listProjects', - cmd' "symbol" ["name"] (matches ++ sandboxes ++ [ + cmdList' "packages" [] [] "list packages" listPackages', + cmdList' "projects" [] [] "list projects" listProjects', + cmdList' "symbol" ["name"] (matches ++ sandboxes ++ [ projectArg `desc` "related project", fileArg `desc` "source file", moduleArg, localsArg, @@ -100,24 +104,33 @@ sourced]) "get module info" modul', + cmd' "resolve" [] (sandboxes ++ [ + moduleArg, localsArg, + projectArg `desc` "module project", + fileArg `desc` "module source file", + exportsArg]) + "resolve module scope (or exports)" + resolve', cmd' "project" [] [ projectArg `desc` "project path or name"] "get project info" project', -- Context commands - cmd' "lookup" ["symbol"] ctx "lookup for symbol" lookup', - cmd' "whois" ["symbol"] ctx "get info for symbol" whois', - cmd' "scope modules" [] ctx "get modules accessible from module or within a project" scopeModules', - cmd' "scope" [] (ctx ++ matches ++ [globalArg]) "get declarations accessible from module or within a project" scope', - cmd' "complete" ["input"] ctx "show completions for input" complete', + cmdList' "lookup" ["symbol"] ctx "lookup for symbol" lookup', + cmdList' "whois" ["symbol"] ctx "get info for symbol" whois', + cmdList' "scope modules" [] ctx "get modules accessible from module or within a project" scopeModules', + cmdList' "scope" [] (ctx ++ matches ++ [globalArg]) "get declarations accessible from module or within a project" scope', + cmdList' "complete" ["input"] (ctx ++ [wideArg]) "show completions for input" complete', -- Tool commands - cmd' "hayoo" ["query"] hayooArgs "find declarations online via Hayoo" hayoo', - cmd' "cabal list" ["packages..."] [] "list cabal packages" cabalList', - cmd' "ghc-mod type" ["line", "column"] (ctx ++ [ghcOpts]) "infer type with 'ghc-mod type'" ghcmodType', - cmd' "ghc-mod check" ["files..."] [sandboxArg, ghcOpts] "check source files" ghcmodCheck', - cmd' "ghc-mod lint" ["file"] [hlintOpts] "lint source file" ghcmodLint', + cmdList' "hayoo" ["query"] hayooArgs "find declarations online via Hayoo" hayoo', + cmdList' "cabal list" ["packages..."] [] "list cabal packages" cabalList', + cmdList' "ghc-mod lang" [] [] "get LANGUAGE pragmas" ghcmodLang', + cmdList' "ghc-mod flags" [] [] "get OPTIONS_GHC pragmas" ghcmodFlags', + cmdList' "ghc-mod type" ["line", "column"] (ctx ++ [ghcOpts]) "infer type with 'ghc-mod type'" ghcmodType', + cmdList' "ghc-mod check" ["files..."] [sandboxArg, ghcOpts] "check source files" ghcmodCheck', + cmdList' "ghc-mod lint" ["file"] [hlintOpts] "lint source file" ghcmodLint', -- Ghc commands - cmd' "ghc eval" ["expr..."] [] "evaluate expression" ghcEval', + cmdList' "ghc eval" ["expr..."] [] "evaluate expression" ghcEval', -- Dump/load commands cmd' "dump" [] (sandboxes ++ [ cacheDir, cacheFile, @@ -140,12 +153,26 @@ Left (CommandError e ds) -> return $ Error e $ M.fromList $ map (first unpack) ds Right r' -> return $ Result $ toJSON r' + cmdList' :: ToJSON a => String -> [String] -> [Opt] -> String -> ([String] -> Opts String -> CommandActionT [a]) -> Cmd CommandAction + cmdList' nm pos named descr act = cmd' nm pos (named ++ [splitRes]) descr act' where + splitRes = flag "split-result" `desc` "split result list and return it with notifications" + act' args os copts = do + rs <- act args os' copts + if flagSet "split-result" isSplit + then do + liftIO $ mapM_ (commandNotify copts . resultPart) rs + return [] + else return rs + where + (isSplit, os') = splitOpts [splitRes] os + -- Command arguments and flags allFlag d = flag "all" `short` ['a'] `desc` d - cacheDir = pathArg `desc` "cache path" - cacheFile = fileArg `desc` "cache file" + cacheDir = req "cache-dir" "path" `desc` "cache path" + cacheFile = req "cache-file" "path" `desc` "cache file" ctx = [fileArg `desc` "source file", sandboxArg] dataArg = req "data" "contents" `desc` "data to pass to command" + exportsArg = flag "exports" `short` ['e'] `desc` "resolve module exports" fileArg = req "file" "path" `short` ['f'] findArg = req "find" "query" `desc` "infix match" ghcOpts = list "ghc" "option" `short` ['g'] `desc` "options to pass to GHC" @@ -156,7 +183,7 @@ hlintOpts = list "hlint" "option" `short` ['h'] `desc` "options to pass to hlint" holdArg = flag "hold" `short` ['h'] `desc` "don't return any response" localsArg = flag "locals" `short` ['l'] `desc` "look in local declarations" - noLastArg = flag "no-last" `desc` "don't select last package version" + noLastArg = flag "no-last" `desc` "select not only last version packages" matches = [prefixArg, findArg] moduleArg = req "module" "name" `short` ['m'] `desc` "module name" packageArg = req "package" "name" `desc` "module package" @@ -171,6 +198,7 @@ sandboxList] sourced = flag "src" `desc` "source files" standaloned = flag "stand" `desc` "standalone files" + wideArg = flag "wide" `short` ['f'] `desc` "wide mode - complete as if there were no import lists" -- | Ping server ping' :: [String] -> Opts String -> CommandActionT Value @@ -199,7 +227,7 @@ updateData (ResultModuleDeclaration md) = do let ModuleId mname mloc = declarationModuleId md - defMod = Module mname Nothing mloc [] mempty mempty + defMod = Module mname Nothing mloc mempty mempty mempty defInspMod = Inspected InspectionNone mloc (Right defMod) dbmod = maybe defInspMod @@ -209,7 +237,7 @@ inspectionResult = fmap (addDeclaration $ moduleDeclaration md) (inspectionResult dbmod) } DB.update (dbVar copts) $ return $ fromModule updatedMod updateData (ResultModuleId (ModuleId mname mloc)) = when (M.notMember mloc $ databaseModules dbval) $ - DB.update (dbVar copts) $ return $ fromModule $ Inspected InspectionNone mloc (Right $ Module mname Nothing mloc [] mempty mempty) + DB.update (dbVar copts) $ return $ fromModule $ Inspected InspectionNone mloc (Right $ Module mname Nothing mloc mempty mempty mempty) updateData (ResultModule m) = DB.update (dbVar copts) $ return $ fromModule $ Inspected InspectionNone (moduleLocation m) (Right m) updateData (ResultInspectedModule m) = DB.update (dbVar copts) $ return $ fromModule m updateData (ResultProject p) = DB.update (dbVar copts) $ return $ fromProject p @@ -381,6 +409,32 @@ [m] -> return m ms' -> commandError "Ambiguous modules" ["modules" .= (map moduleId ms')] + -- | Resolve module scope + resolve' :: [String] -> Opts String -> CommandActionT Module + resolve' _ as copts = do + dbval <- liftM (localsDatabase as) $ getDb copts + proj <- mapErrorT (fmap $ strMsg +++ id) $ traverse (findProject copts) $ arg "project" as + cabal <- mapErrorT (fmap $ strMsg +++ id) $ getCabal copts as + file' <- mapErrorT (fmap $ strMsg +++ id) $ traverse (findPath copts) $ arg "file" as + let + filters = allOf $ catMaybes [ + fmap inProject proj, + fmap inFile file', + fmap inModule (arg "module" as), + Just byFile] + rs <- mapErrorT (fmap $ strMsg +++ id) $ + (newest as . filter (filters . moduleId)) <$> maybe + (return $ allModules dbval) + (mapErrorStr . findModule dbval) + (arg "module" as) + let + cabaldb = filterDB (restrictCabal cabal) (const True) dbval + getScope = if flagSet "exports" as then exportsModule else scopeModule + case rs of + [] -> commandError "Module not found" [] + [m] -> return $ getScope $ resolveOne cabaldb m + ms' -> commandError "Ambiguous modules" ["modules" .= (map moduleId ms')] + -- | Get project info project' :: [String] -> Opts String -> CommandActionT Project project' _ as copts = do @@ -425,7 +479,7 @@ complete' [input] as copts = do dbval <- getDb copts (srcFile, cabal) <- getCtx copts as - mapErrorStr $ completions dbval cabal srcFile input + mapErrorStr $ completions dbval cabal srcFile input (flagSet "wide" as) complete' _ _ _ = commandError "Invalid arguments" [] -- | Hayoo @@ -443,6 +497,14 @@ cabalList' :: [String] -> Opts String -> CommandActionT [Cabal.CabalPackage] cabalList' qs _ _ = mapErrorStr $ Cabal.cabalList qs + -- | Ghc-mod lang + ghcmodLang' :: [String] -> Opts String -> CommandActionT [String] + ghcmodLang' _ _ _ = mapErrorStr GhcMod.langs + + -- | Ghc-mod flags + ghcmodFlags' :: [String] -> Opts String -> CommandActionT [String] + ghcmodFlags' _ _ _ = mapErrorStr GhcMod.flags + -- | Ghc-mod type ghcmodType' :: [String] -> Opts String -> CommandActionT [GhcMod.TypedRegion] ghcmodType' [line] as copts = ghcmodType' [line, "1"] as copts @@ -451,9 +513,9 @@ column' <- maybe (commandError "column must be a number" []) return $ readMaybe column dbval <- getDb copts (srcFile, cabal) <- getCtx copts as - (srcFile', m, mproj) <- mapErrorStr $ fileCtx dbval srcFile + (srcFile', _, _) <- mapErrorStr $ fileCtx dbval srcFile mapErrorStr $ GhcMod.waitMultiGhcMod (commandGhcMod copts) srcFile' $ - GhcMod.typeOf (listArg "ghc" as) cabal srcFile' mproj (moduleName m) line' column' + GhcMod.typeOf (listArg "ghc" as) cabal srcFile' line' column' ghcmodType' [] _ _ = commandError "Specify line" [] ghcmodType' _ _ _ = commandError "Too much arguments" [] @@ -479,11 +541,11 @@ -- | Evaluate expression ghcEval' :: [String] -> Opts String -> CommandActionT [Value] - ghcEval' exprs _ copts = mapErrorStr $ liftM (map toValue) $ - waitGhc (commandGhc copts) $ mapM (try . evaluate) exprs + ghcEval' exprs _ copts = mapErrorStr $ liftM (map toValue) $ liftTask $ + pushTask (commandGhc copts) $ mapM (try . evaluate) exprs where - toValue :: Either String String -> Value - toValue (Left e) = object ["fail" .= e] + toValue :: Either SomeException String -> Value + toValue (Left (SomeException e)) = object ["fail" .= show e] toValue (Right s) = toJSON s -- | Dump database info @@ -501,7 +563,7 @@ if flagSet "stand" as then standaloneDB dbval else mempty, mconcat $ map (`cabalDB` dbval) cabals, mconcat $ map (`projectDB` dbval) ps', - filterDB (\m -> any (`inFile` moduleId m) fs') (const False) dbval] + filterDB (\m -> any (`inFile` m) fs') (const False) dbval] void $ runMaybeT $ msum [ do @@ -632,7 +694,7 @@ | flagSet "locals" as = databaseLocals | otherwise = id --- | Select newest packages if 'no-last' flag set +-- | Select newest packages if 'no-last' flag not set newest :: Symbol a => Opts String -> [a] -> [a] newest as | flagSet "no-last" as = id @@ -659,6 +721,7 @@ settings = Update.Settings (commandDatabase copts) (commandReadCache copts) + (commandWriteCache copts) (commandNotify copts . Notification . toJSON) (listArg "ghc" as) @@ -672,7 +735,7 @@ Nothing -> id Just str -> filter (match' str) where - match' str m = str `isInfixOf` declarationName (moduleDeclaration m) + match' str m = fromString str `T.isInfixOf` declarationName (moduleDeclaration m) -- | Filter declarations with prefix match prefMatch :: Opts String -> [ModuleDeclaration] -> [ModuleDeclaration] @@ -681,6 +744,5 @@ Just (qname, pref) -> filter (match' qname pref) where match' qname pref m = - pref `isPrefixOf` declarationName (moduleDeclaration m) && - maybe True (== moduleIdName (declarationModuleId m)) qname - + fromString pref `T.isPrefixOf` declarationName (moduleDeclaration m) && + maybe True (== moduleIdName (declarationModuleId m)) (fmap fromString qname)
src/HsDev/Commands.hs view
@@ -19,17 +19,19 @@ ) where import Control.Applicative -import Control.Arrow (Arrow(second)) import Control.Monad.Error import Data.List import Data.Maybe import qualified Data.Map as M (lookup) +import Data.String (fromString) +import qualified Data.Text as T (isPrefixOf, split, unpack) import Data.Traversable (traverse) import System.Directory (canonicalizePath) import HsDev.Database import HsDev.Project import HsDev.Symbols +import HsDev.Symbols.Resolve import HsDev.Symbols.Util -- | Find declaration by name @@ -37,14 +39,14 @@ findDeclaration db ident = return $ selectDeclarations checkName db where checkName :: ModuleDeclaration -> Bool checkName m = - (declarationName (moduleDeclaration m) == iname) && - (maybe True (moduleIdName (declarationModuleId m) ==) qname) + (declarationName (moduleDeclaration m) == fromString iname) && + (maybe True ((moduleIdName (declarationModuleId m) ==) . fromString) qname) (qname, iname) = splitIdentifier ident -- | Find module by name findModule :: Database -> String -> ErrorT String IO [Module] -findModule db mname = return $ selectModules ((== mname) . moduleName) db +findModule db mname = return $ selectModules ((== fromString mname) . moduleName) db -- | Find module in file fileModule :: Database -> FilePath -> ErrorT String IO Module @@ -59,7 +61,7 @@ maybe (throwError $ "Project " ++ p' ++ " not found") return $ M.lookup p' $ databaseProjects db --- | Lookup visible symbol +-- | Lookup visible within project symbol lookupSymbol :: Database -> Cabal -> FilePath -> String -> ErrorT String IO [ModuleDeclaration] lookupSymbol db cabal file ident = do (_, mthis, mproj) <- fileCtx db file @@ -75,14 +77,15 @@ -- | Whois symbol in scope whois :: Database -> Cabal -> FilePath -> String -> ErrorT String IO [ModuleDeclaration] whois db cabal file ident = do - (_, mthis, _) <- fileCtx db file - liftM - (filter $ checkModule $ allOf [ - restrictCabal cabal, - inScope mthis qname]) - (newestPackage <$> findDeclaration db iname) + (_, mthis, mproj) <- fileCtx db file + return $ + newestPackage $ filter (checkDecl . moduleDeclaration) $ + moduleModuleDeclarations $ scopeModule $ + resolveOne (fileDeps file cabal mproj db) $ + moduleLocals mthis where (qname, iname) = splitIdentifier ident + checkDecl d = fmap fromString qname `elem` scopes d && declarationName d == fromString iname -- | Accessible modules scopeModules :: Database -> Cabal -> FilePath -> ErrorT String IO [Module] @@ -100,35 +103,33 @@ -- | Symbols in scope scope :: Database -> Cabal -> FilePath -> Bool -> ErrorT String IO [ModuleDeclaration] scope db cabal file False = do - (_, mthis, _) <- fileCtx db file - depModules <- liftM (filter ((`imported` (moduleImports' mthis)) . moduleId)) $ - scopeModules db cabal file - return $ concatMap moduleModuleDeclarations $ mthis : depModules + (_, mthis, mproj) <- fileCtx db file + return $ moduleModuleDeclarations $ scopeModule $ resolveOne (fileDeps file cabal mproj db) mthis scope db cabal file True = concatMap moduleModuleDeclarations <$> scopeModules db cabal file -- | Completions -completions :: Database -> Cabal -> FilePath -> String -> ErrorT String IO [ModuleDeclaration] -completions db cabal file prefix = do - (_, mthis, _) <- fileCtx db file - decls <- scope db cabal file False - return [decl | - decl <- decls, - imp <- filter ((== moduleIdName (declarationModuleId decl)) . importModuleName) $ - moduleImports' mthis, - qname `elem` catMaybes [ - if not (importIsQualified imp) then Just Nothing else Nothing, - Just $ Just $ importModuleName imp, - fmap Just $ importAs imp], - iname `isPrefixOf` (declarationName . moduleDeclaration $ decl)] +completions :: Database -> Cabal -> FilePath -> String -> Bool -> ErrorT String IO [ModuleDeclaration] +completions db cabal file prefix wide = do + (_, mthis, mproj) <- fileCtx db file + return $ + newestPackage $ filter (checkDecl . moduleDeclaration) $ + moduleModuleDeclarations $ scopeModule $ + resolveOne (fileDeps file cabal mproj db) $ + dropImportLists mthis where (qname, iname) = splitIdentifier prefix + checkDecl d = fmap fromString qname `elem` scopes d && fromString iname `T.isPrefixOf` declarationName d + dropImportLists m + | wide = m { moduleImports = map dropList (moduleImports m) } + | otherwise = m + dropList i = i { importList = Nothing } -- | Module completions moduleCompletions :: Database -> [Module] -> String -> ErrorT String IO [String] -moduleCompletions _ ms prefix = return $ nub $ completions' $ map moduleName ms where +moduleCompletions _ ms prefix = return $ map T.unpack $ nub $ completions' $ map moduleName ms where completions' = mapMaybe getNext where getNext m - | prefix `isPrefixOf` m = listToMaybe $ map snd $ dropWhile (uncurry (==)) $ zip (splitBy '.' prefix) (splitBy '.' m) + | fromString prefix `T.isPrefixOf` m = listToMaybe $ map snd $ dropWhile (uncurry (==)) $ zip (T.split (== '.') $ fromString prefix) (T.split (== '.') m) | otherwise = Nothing -- | Check module @@ -148,12 +149,12 @@ visibleFrom (Just p) this m = visible p (moduleId this) m visibleFrom Nothing this m = (moduleId this) == m || byCabal m -splitBy :: Char -> String -> [String] -splitBy ch = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break (== ch)) - -- | Get module imports with Prelude and self import moduleImports' :: Module -> [Import] -moduleImports' m = Import "Prelude" False Nothing Nothing : Import (moduleName m) False Nothing Nothing : moduleImports m +moduleImports' m = + import_ (fromString "Prelude") : + import_ (moduleName m) : + moduleImports m -- | Split identifier into module name and identifier itself splitIdentifier :: String -> (Maybe String, String) @@ -179,3 +180,12 @@ mproj <- liftIO $ locateProject file' mproj' <- traverse (getProject db) mproj return (file', Nothing, mproj') + +-- | Restrict only modules file depends on +fileDeps :: FilePath -> Cabal -> Maybe Project -> Database -> Database +fileDeps file cabal mproj = filterDB fileDeps' (const True) where + fileDeps' = liftM2 (||) + (maybe (const True) inProject mproj) + (liftM2 (&&) + (restrictCabal cabal) + (maybe (const True) inDepsOf (join $ fileTarget <$> mproj <*> pure file)))
src/HsDev/Database.hs view
@@ -6,7 +6,7 @@ fromModule, fromProject, filterDB, projectDB, cabalDB, standaloneDB, - selectModules, selectDeclarations, lookupModule, lookupFile, + selectModules, selectDeclarations, lookupModule, lookupFile, refineProject, getInspected, append, remove, @@ -107,22 +107,22 @@ databaseProjects = M.singleton (projectCabal p) p } -- | Filter database by predicate -filterDB :: (Module -> Bool) -> (Project -> Bool) -> Database -> Database +filterDB :: (ModuleId -> Bool) -> (Project -> Bool) -> Database -> Database filterDB m p db = mempty { - databaseModules = M.filter (either (const False) m . inspectionResult) (databaseModules db), + databaseModules = M.filter (either (const False) (m . moduleId) . inspectionResult) (databaseModules db), databaseProjects = M.filter p (databaseProjects db) } -- | Project database projectDB :: Project -> Database -> Database -projectDB proj = filterDB (inProject proj . moduleId) (((==) `on` projectCabal) proj) +projectDB proj = filterDB (inProject proj) (((==) `on` projectCabal) proj) -- | Cabal database cabalDB :: Cabal -> Database -> Database -cabalDB cabal = filterDB (inCabal cabal . moduleId) (const False) +cabalDB cabal = filterDB (inCabal cabal) (const False) -- | Standalone database standaloneDB :: Database -> Database -standaloneDB db = filterDB (check' . moduleId) (const False) db where +standaloneDB db = filterDB (check') (const False) db where check' m = noProject m && byFile m noProject m = all (not . flip inProject m) ps ps = M.elems $ databaseProjects db @@ -144,6 +144,10 @@ -- | Lookup module by its source file lookupFile :: FilePath -> Database -> Maybe Module lookupFile f = listToMaybe . selectModules (inFile f . moduleId) + +-- | Refine project +refineProject :: Database -> Project -> Maybe Project +refineProject db proj = M.lookup (projectCabal proj) $ databaseProjects db -- | Get inspected module getInspected :: Database -> Module -> InspectedModule
src/HsDev/Database/Update.hs view
@@ -17,9 +17,10 @@ ) where import Control.Applicative -import Control.Monad.CatchIO +import Control.Monad.Catch import Control.Monad.Error import Control.Monad.Reader +import Control.Monad.Writer import Data.Aeson import Data.Aeson.Types import qualified Data.HashMap.Strict as HM @@ -27,7 +28,7 @@ import Data.Map (Map) import qualified Data.Map as M import Data.Maybe (mapMaybe, isJust) -import Data.Monoid +import qualified Data.Text as T (unpack) import System.Directory (canonicalizePath) import qualified HsDev.Cache.Structured as Cache @@ -96,16 +97,30 @@ data Settings = Settings { database :: Async Database, databaseCacheReader :: (FilePath -> ErrorT String IO Structured) -> IO (Maybe Database), + databaseCacheWriter :: Database -> IO (), onStatus :: Task -> IO (), ghcOptions :: [String] } -newtype UpdateDB m a = UpdateDB { runUpdateDB :: ReaderT Settings m a } - deriving (Applicative, Monad, MonadIO, MonadCatchIO, Functor, MonadReader Settings) +newtype UpdateDB m a = UpdateDB { runUpdateDB :: ReaderT Settings (WriterT [ModuleLocation] m) a } + deriving (Applicative, Monad, MonadIO, MonadThrow, MonadCatch, Functor, MonadReader Settings, MonadWriter [ModuleLocation]) -- | Run `UpdateDB` monad -updateDB :: Monad m => Settings -> ErrorT String (UpdateDB m) () -> m () -updateDB sets act = runUpdateDB (runErrorT act >> return ()) `runReaderT` sets +updateDB :: MonadIO m => Settings -> ErrorT String (UpdateDB m) () -> m () +updateDB sets act = do + updatedMods <- execWriterT (runUpdateDB (runErrorT act >> return ()) `runReaderT` sets) + wait $ database sets + dbval <- liftIO $ readAsync $ database sets + let + cabals = nub $ mapMaybe moduleCabal_ updatedMods + projs = nub $ mapMaybe moduleProject_ updatedMods + stand = any moduleStandalone updatedMods + modifiedDb = mconcat $ concat [ + map (`cabalDB` dbval) cabals, + map (`projectDB` dbval) projs, + [standaloneDB dbval | stand]] + liftIO $ databaseCacheWriter sets modifiedDb + -- | Post status postStatus :: (MonadIO m, MonadReader Settings m) => Task -> m () postStatus s = do @@ -120,13 +135,15 @@ wait db -- | Update task result to database -updater :: (MonadIO m, MonadReader Settings m) => m Database -> m () +updater :: (MonadIO m, MonadReader Settings m, MonadWriter [ModuleLocation] m) => m Database -> m () updater act = do db <- asks database - act >>= update db . return + db' <- act + update db $ return db' + tell $ map moduleLocation $ allModules db' -- | Load data from cache and wait -loadCache :: (MonadIO m, MonadReader Settings m) => (FilePath -> ErrorT String IO Structured) -> m () +loadCache :: (MonadIO m, MonadReader Settings m, MonadWriter [ModuleLocation] m) => (FilePath -> ErrorT String IO Structured) -> m () loadCache act = do cacheReader <- asks databaseCacheReader mdat <- liftIO $ cacheReader act @@ -167,7 +184,7 @@ readDB = asks database >>= liftIO . readAsync -- | Scan module -scanModule :: MonadCatchIO m => [String] -> ModuleLocation -> ErrorT String (UpdateDB m) () +scanModule :: (MonadIO m, MonadCatch m) => [String] -> ModuleLocation -> ErrorT String (UpdateDB m) () scanModule opts mloc = runTask "scanning" (subject mloc ["module" .= mloc]) $ do im <- liftErrorT $ S.scanModule opts mloc updater $ return $ fromModule im @@ -175,10 +192,10 @@ return () -- | Scan modules -scanModules :: MonadCatchIO m => [String] -> [S.ModuleToScan] -> ErrorT String (UpdateDB m) () +scanModules :: (MonadIO m, MonadCatch m) => [String] -> [S.ModuleToScan] -> ErrorT String (UpdateDB m) () scanModules opts ms = do dbval <- readDB - ms' <- liftErrorT $ filterM (S.changedModule dbval opts . fst) ms + ms' <- liftErrorT $ filterM (\m -> S.changedModule dbval (opts ++ snd m) (fst m)) ms runTasks $ [scanProjectFile opts p >> return () | p <- ps] ++ [scanModule (opts ++ snd m) (fst m) | m <- ms'] @@ -188,18 +205,23 @@ toProj _ = Nothing -- | Scan source file -scanFile :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) () +scanFile :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) () scanFile opts fpath = do + dbval <- readDB fpath' <- liftIO $ canonicalizePath fpath - mproj <- liftIO $ locateProject fpath' + mloc <- case lookupFile fpath' dbval of + Just m -> return $ moduleLocation m + Nothing -> do + mproj <- liftIO $ locateProject fpath' + return $ FileModule fpath' mproj + dirty <- liftErrorT $ S.changedModule dbval opts mloc let - mtarget = mproj >>= (`fileTarget` fpath') + mtarget = moduleProject_ mloc >>= (`fileTarget` fpath') fileExts = maybe [] (extensionsOpts . infoExtensions) mtarget - - scanModule (opts ++ fileExts) (FileModule fpath' mproj) + when dirty $ scanModule (opts ++ fileExts) mloc -- | Scan cabal modules -scanCabal :: MonadCatchIO m => [String] -> Cabal -> ErrorT String (UpdateDB m) () +scanCabal :: (MonadIO m, MonadCatch m) => [String] -> Cabal -> ErrorT String (UpdateDB m) () scanCabal opts cabalSandbox = runTask "scanning" (subject cabalSandbox ["sandbox" .= cabalSandbox]) $ do loadCache $ Cache.loadCabal cabalSandbox dbval <- readDB @@ -210,17 +232,17 @@ updater $ return $ 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 (moduleName m) docs + setDocs' docs m = maybe m (`setDocs` m) $ M.lookup (T.unpack $ moduleName m) docs -- | Scan project file -scanProjectFile :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) Project +scanProjectFile :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) Project scanProjectFile opts cabal = runTask "scanning" (subject cabal ["file" .= cabal]) $ do proj <- liftErrorT $ S.scanProjectFile opts cabal updater $ return $ fromProject proj return proj -- | Scan project -scanProject :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) () +scanProject :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) () scanProject opts cabal = runTask "scanning" (subject (project cabal) ["project" .= cabal]) $ do proj <- scanProjectFile opts cabal loadCache $ Cache.loadProject $ projectCabal proj @@ -228,7 +250,7 @@ scanModules opts sources -- | Scan directory for source files and projects -scanDirectory :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) () +scanDirectory :: (MonadIO m, MonadCatch m) => [String] -> FilePath -> ErrorT String (UpdateDB m) () scanDirectory opts dir = runTask "scanning" (subject dir ["path" .= dir]) $ do (projSrcs, standSrcs) <- runTask "getting list of sources" [] $ liftErrorT $ S.enumDirectory dir
src/HsDev/Inspect.hs view
@@ -14,9 +14,12 @@ import qualified Control.Exception as E import Control.Monad import Control.Monad.Error -import Data.List (intercalate, find) +import Data.Function (on) +import Data.List import Data.Map (Map) import Data.Maybe (fromMaybe, mapMaybe, catMaybes) +import Data.Ord (comparing) +import Data.String (fromString) import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds) import Data.Traversable (traverse, sequenceA) import qualified Data.Map as M @@ -25,6 +28,7 @@ import qualified System.Directory as Dir import System.IO import System.FilePath +import Data.Generics.Uniplate.Data import qualified Name (Name, getOccString, occNameString) import qualified Module (moduleNameString) @@ -42,71 +46,100 @@ analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module analyzeModule exts file source = case H.parseFileContentsWithMode pmode source' of H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason - H.ParseOk (H.Module _ (H.ModuleName mname) _ _ _ imports declarations) -> Right $ Module { - moduleName = mname, + H.ParseOk (H.Module _ (H.ModuleName mname) _ _ mexports imports declarations) -> Right Module { + moduleName = fromString mname, moduleDocs = Nothing, moduleLocation = ModuleSource Nothing, - moduleExports = [], + moduleExports = fmap (concatMap getExports) mexports, moduleImports = map getImport imports, - moduleDeclarations = M.fromList $ map (declarationName &&& id) $ getDecls declarations } + moduleDeclarations = sortDeclarations $ getDecls declarations } where pmode :: H.ParseMode pmode = H.defaultParseMode { H.parseFilename = fromMaybe (H.parseFilename H.defaultParseMode) file, H.baseLanguage = H.Haskell2010, - H.extensions = map H.parseExtension exts } + H.extensions = H.glasgowExts ++ map H.parseExtension exts, + H.fixities = Just H.baseFixities } -- 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 +getExports :: H.ExportSpec -> [Export] +getExports (H.EModuleContents (H.ModuleName m)) = [ExportModule $ fromString m] +getExports e = map (uncurry ExportName . (fmap fromString *** fromString) . identOfQName) $ childrenBi e + getImport :: H.ImportDecl -> Import -getImport d = Import (mname (H.importModule d)) (H.importQualified d) (fmap mname $ H.importAs d) (Just $ toPosition $ H.importLoc d) where - mname (H.ModuleName n) = n +getImport d = Import + (mname (H.importModule d)) + (H.importQualified d) + (mname <$> H.importAs d) + (importLst <$> H.importSpecs d) + (Just $ toPosition $ H.importLoc d) + where + mname (H.ModuleName n) = fromString n + importLst (hiding, specs) = ImportList hiding $ map (fromString . identOfName) (concatMap childrenBi specs :: [H.Name]) getDecls :: [H.Decl] -> [Declaration] -getDecls decls = map addLocals declInfos ++ filter noInfo defs where - declInfos = concatMap getDecl decls - addLocals :: Declaration -> Declaration - addLocals decl = decl `where_` maybe [] locals def where - def = find (\d -> declarationName d == declarationName decl) defs - defs = concatMap getDef decls - noInfo :: Declaration -> Bool - noInfo d = declarationName d `notElem` names - names = map declarationName declInfos +getDecls decls = + map mergeDecls . + groupBy ((==) `on` declarationName) . + sortBy (comparing declarationName) $ + concatMap getDecl decls ++ concatMap getDef decls + where + mergeDecls :: [Declaration] -> Declaration + mergeDecls [] = error "Impossible" + mergeDecls ds = Declaration + (declarationName $ head ds) + Nothing + Nothing + (msum $ map declarationDocs ds) + (minimum <$> mapM declarationPosition ds) + (foldr1 mergeInfos $ map declaration ds) + mergeInfos :: DeclarationInfo -> DeclarationInfo -> DeclarationInfo + mergeInfos (Function ln ld) (Function rn rd) = Function (ln `mplus` rn) (ld ++ rd) + mergeInfos l _ = l + getBinds :: H.Binds -> [Declaration] getBinds (H.BDecls decls) = getDecls decls getBinds _ = [] getDecl :: H.Decl -> [Declaration] -getDecl decl = case decl of - H.TypeSig loc names typeSignature -> map - (\n -> setPosition loc (Declaration (identOfName n) Nothing Nothing (Function (Just $ oneLinePrint typeSignature) []))) - names - H.TypeDecl loc n args _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (Type $ TypeInfo Nothing (map oneLinePrint args) Nothing)] - H.DataDecl loc dataOrNew ctx n args _ _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (ctor dataOrNew $ TypeInfo (makeCtx ctx) (map oneLinePrint args) Nothing)] - H.GDataDecl loc dataOrNew ctx n args _ _ _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (ctor dataOrNew $ TypeInfo (makeCtx ctx) (map oneLinePrint args) Nothing)] - H.ClassDecl loc ctx n args _ _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (Class $ TypeInfo (makeCtx ctx) (map oneLinePrint args) Nothing)] +getDecl decl' = case decl' of + H.TypeSig loc names typeSignature -> [mkFun loc n (Function (Just $ fromString $ oneLinePrint typeSignature) []) | n <- names] + H.TypeDecl loc n args _ -> [mkType loc n Type args] + H.DataDecl loc dataOrNew ctx n args _ _ -> [mkType loc n (ctor dataOrNew `withCtx` ctx) args] + H.GDataDecl loc dataOrNew ctx n args _ _ _ -> [mkType loc n (ctor dataOrNew `withCtx` ctx) args] + H.ClassDecl loc ctx n args _ _ -> [mkType loc n (Class `withCtx` ctx) args] _ -> [] where + mkFun :: H.SrcLoc -> H.Name -> DeclarationInfo -> Declaration + mkFun loc n = setPosition loc . decl (fromString $ identOfName n) + + mkType :: H.SrcLoc -> H.Name -> (TypeInfo -> DeclarationInfo) -> [H.TyVarBind] -> Declaration + mkType loc n ctor' args = setPosition loc $ decl (fromString $ identOfName n) $ ctor' $ TypeInfo Nothing (map (fromString . oneLinePrint) args) Nothing + + withCtx :: (TypeInfo -> DeclarationInfo) -> H.Context -> TypeInfo -> DeclarationInfo + withCtx ctor' ctx tinfo = ctor' (tinfo { typeInfoContext = makeCtx ctx }) + ctor :: H.DataOrNew -> TypeInfo -> DeclarationInfo ctor H.DataType = Data ctor H.NewType = NewType makeCtx [] = Nothing - makeCtx ctx = Just $ intercalate ", " $ map oneLinePrint ctx + makeCtx ctx = Just $ fromString $ intercalate ", " $ map oneLinePrint ctx oneLinePrint :: H.Pretty a => a -> String oneLinePrint = H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode getDef :: H.Decl -> [Declaration] getDef (H.FunBind []) = [] -getDef (H.FunBind matches@(H.Match loc n _ _ _ _ : _)) = [setPosition loc $ Declaration (identOfName n) Nothing Nothing fun] where +getDef (H.FunBind matches@(H.Match loc n _ _ _ _ : _)) = [setPosition loc $ decl (fromString $ identOfName n) fun] where fun = Function Nothing $ concatMap (getBinds . matchBinds) matches matchBinds (H.Match _ _ _ _ _ binds) = binds -getDef (H.PatBind loc pat _ binds) = map (\name -> setPosition loc (Declaration (identOfName name) Nothing Nothing (Function Nothing $ getBinds binds))) (names pat) where +getDef (H.PatBind loc pat _ binds) = map (\name -> setPosition loc (decl (fromString $ identOfName name) (Function Nothing $ getBinds binds))) (names pat) where names :: H.Pat -> [H.Name] names (H.PVar n) = [n] names (H.PNPlusK n _) = [n] @@ -133,6 +166,11 @@ fieldNames H.PFieldWildcard = [] getDef _ = [] +identOfQName :: H.QName -> (Maybe String, String) +identOfQName (H.Qual (H.ModuleName mname) name) = (Just mname, identOfName name) +identOfQName (H.UnQual name) = (Nothing, identOfName name) +identOfQName (H.Special sname) = (Nothing, H.prettyPrint sname) + identOfName :: H.Name -> String identOfName name = case name of H.Ident s -> s @@ -148,7 +186,7 @@ documentationMap :: Doc.Interface -> Map String String documentationMap iface = M.fromList $ concatMap toDoc $ Doc.ifaceExportItems iface where toDoc :: Doc.ExportItem Name.Name -> [(String, String)] - toDoc (Doc.ExportDecl decl docs _ _ _ _) = maybe [] (zip (extractNames decl) . repeat) $ extractDocs docs + toDoc (Doc.ExportDecl decl' docs _ _ _ _) = maybe [] (zip (extractNames decl') . repeat) $ extractDocs docs toDoc _ = [] extractNames :: HsDecls.LHsDecl Name.Name -> [String] @@ -161,7 +199,7 @@ _ -> [] extractDocs :: Doc.DocForDecl Name.Name -> Maybe String - extractDocs (mbDoc, _) = fmap printDoc $ Doc.documentationDoc mbDoc where + extractDocs (mbDoc, _) = printDoc <$> Doc.documentationDoc mbDoc where printDoc :: Doc.Doc Name.Name -> String printDoc Doc.DocEmpty = "" printDoc (Doc.DocAppend l r) = printDoc l ++ printDoc r @@ -191,11 +229,12 @@ -- | Adds documentation to declaration addDoc :: Map String String -> Declaration -> Declaration -addDoc docsMap decl = decl { declarationDocs = M.lookup (declarationName decl) docsMap } +addDoc docsMap decl' = decl' { declarationDocs = M.lookup (declarationName decl') docsMap' } where + docsMap' = M.mapKeys fromString . M.map fromString $ docsMap -- | Adds documentation to all declarations in module addDocs :: Map String String -> Module -> Module -addDocs docsMap m = m { moduleDeclarations = M.map (addDoc docsMap) (moduleDeclarations m) } +addDocs docsMap m = m { moduleDeclarations = map (addDoc docsMap) (moduleDeclarations m) } -- | Inspect contents inspectContents :: String -> [String] -> String -> ErrorT String IO InspectedModule @@ -221,9 +260,9 @@ proj <- liftIO $ locateProject file absFilename <- liftIO $ Dir.canonicalizePath file inspect (FileModule absFilename proj) (fileInspection absFilename opts) $ do - docsMap <- case hdocsWorkaround of - True -> liftIO $ hdocsProcess absFilename opts - False -> liftIO $ fmap (fmap documentationMap . lookup absFilename) $ do + docsMap <- liftIO $ if hdocsWorkaround + then hdocsProcess absFilename opts + else fmap (fmap documentationMap . lookup absFilename) $ do is <- E.catch (Doc.createInterfaces ([Doc.Flag_Verbosity "0", Doc.Flag_NoWarnings] ++ map Doc.Flag_OptGhc opts) [absFilename]) noReturn forM is $ \i -> do mfile <- Dir.canonicalizePath $ Doc.ifaceOrigFilename i @@ -244,21 +283,27 @@ fileInspection :: FilePath -> [String] -> ErrorT String IO Inspection fileInspection f opts = do tm <- liftIO $ Dir.getModificationTime f - return $ InspectionAt (utcTimeToPOSIXSeconds tm) opts + return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ nub opts -- | Enumerate project dirs projectDirs :: Project -> ErrorT String IO [Extensions FilePath] projectDirs p = do p' <- loadProject p - return $ map (fmap (projectPath p' </>)) $ maybe [] sourceDirs $ projectDescription p' + return $ nub $ map (fmap (normalise . (projectPath p' </>))) $ maybe [] sourceDirs $ projectDescription p' -- | Enumerate project source files projectSources :: Project -> ErrorT String IO [Extensions FilePath] projectSources p = do dirs <- projectDirs p let - enumHs = liftM (filter haskellSource) . traverseDirectory - liftIO $ liftM concat $ mapM (liftM sequenceA . traverse (liftIO . enumHs)) dirs + enumCabals = liftM (map takeDirectory . filter cabalFile) . traverseDirectory + dirs' = map entity dirs + -- enum inner projects and dont consider them as part of this project + subProjs <- liftIO $ liftM (delete (projectPath p) . nub . concat) $ mapM (liftIO . enumCabals) dirs' + let + enumHs = liftM (filter thisProjectSource) . traverseDirectory + thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs) + liftIO $ liftM (nub . concat) $ mapM (liftM sequenceA . traverse (liftIO . enumHs)) dirs -- | Inspect project inspectProject :: [String] -> Project -> ErrorT String IO (Project, [InspectedModule])
src/HsDev/Project.hs view
@@ -282,6 +282,7 @@ data Extensions a = Extensions { extensions :: [Extension], entity :: a } + deriving (Eq, Read, Show) instance Functor Extensions where fmap f (Extensions e x) = Extensions e (f x) @@ -326,7 +327,7 @@ -- | Returns source dirs for library, executables and tests sourceDirs :: ProjectDescription -> [Extensions FilePath] -sourceDirs = concatMap dirs . infos where +sourceDirs = nub . concatMap dirs . infos where dirs i = map (`withExtensions` i) $ infoSourceDirs i parseDT :: Distribution.Text.Text a => String -> String -> Parser a
src/HsDev/Scan.hs view
@@ -60,7 +60,9 @@ -- | Scan module scanModule :: [String] -> ModuleLocation -> ErrorT String IO InspectedModule -scanModule opts (FileModule f _) = inspectFile opts f >>= traverse (runGhcMod defaultOptions . inferTypes opts Cabal) +scanModule opts (FileModule f _) = inspectFile opts f >>= traverse infer' where + infer' m = mapErrorT (withCurrentDirectory (sourceModuleRoot (moduleName m) f)) $ + runGhcMod defaultOptions $ inferTypes opts Cabal m scanModule opts (CabalModule c p n) = browse opts c n p scanModule _ (ModuleSource _) = throwError "Can inspect only modules in file or cabal"
src/HsDev/Scan/Browse.hs view
@@ -3,10 +3,10 @@ browseFilter, browse ) where -import Control.Arrow import Control.Monad.Error +import Data.List (nub) import Data.Maybe -import qualified Data.Map as M +import Data.String (fromString) import Text.Read (readMaybe) import HsDev.Cabal @@ -50,30 +50,37 @@ browseModule cabal m = do mi <- lift (GHC.getModuleInfo m) >>= maybe (throwError "Can't find module info") return ds <- mapM (toDecl mi) (GHC.modInfoExports mi) - return $ Module { - moduleName = GHC.moduleNameString (GHC.moduleName m), + let + thisModule = GHC.moduleNameString (GHC.moduleName m) + return Module { + moduleName = fromString thisModule, moduleDocs = Nothing, - moduleLocation = mloc, - moduleExports = [], - moduleImports = [], - moduleDeclarations = M.fromList (map (declarationName &&& id) ds) } + moduleLocation = thisLoc, + moduleExports = Just $ map (ExportName Nothing . declarationName) ds, + moduleImports = [import_ iname | iname <- nub (mapMaybe definedModule ds), iname /= fromString thisModule], + moduleDeclarations = sortDeclarations ds } where - mloc = CabalModule cabal (readMaybe $ GHC.packageIdString $ GHC.modulePackageId m) (GHC.moduleNameString $ GHC.moduleName m) + thisLoc = moduleIdLocation $ mloc m + mloc m' = ModuleId (fromString mname') $ + CabalModule cabal (readMaybe $ GHC.packageIdString $ GHC.modulePackageId m') mname' + where + mname' = GHC.moduleNameString $ GHC.moduleName m' toDecl minfo n = do tyInfo <- lift $ GHC.modInfoLookupName minfo n tyResult <- lift $ maybe (inModuleSource n) (return . Just) tyInfo dflag <- lift GHC.getSessionDynFlags - return $ Declaration - (GHC.getOccString n) - Nothing - Nothing - (fromMaybe (Function Nothing []) (tyResult >>= showResult dflag)) + let + decl' = decl (fromString $ GHC.getOccString n) $ fromMaybe + (Function Nothing []) + (tyResult >>= showResult dflag) + return $ decl' `definedIn` mloc (GHC.nameModule n) + definedModule = fmap moduleIdName . declarationDefined showResult :: GHC.DynFlags -> GHC.TyThing -> Maybe DeclarationInfo - showResult dflags (GHC.AnId i) = Just $ Function (Just $ formatType dflags GHC.varType i) [] + showResult dflags (GHC.AnId i) = Just $ Function (Just $ fromString $ formatType dflags GHC.varType i) [] showResult dflags (GHC.AConLike c) = case c of - GHC.RealDataCon d -> Just $ Function (Just $ formatType dflags GHC.dataConRepType d) [] - GHC.PatSynCon p -> Just $ Function (Just $ formatType dflags GHC.patSynType p) [] - showResult _ (GHC.ATyCon t) = Just $ tcon $ TypeInfo Nothing (map GHC.getOccString $ GHC.tyConTyVars t) Nothing where + GHC.RealDataCon d -> Just $ Function (Just $ fromString $ formatType dflags GHC.dataConRepType d) [] + GHC.PatSynCon p -> Just $ Function (Just $ fromString $ formatType dflags GHC.patSynType p) [] + 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 @@ -124,4 +131,4 @@ styleUnqualified = GHC.mkUserStyle GHC.neverQualify GHC.AllTheWay tryT :: (Monad m, Error e) => ErrorT e m a -> ErrorT e m (Maybe a) -tryT act = catchError (liftM Just $ act) (const $ return Nothing) +tryT act = catchError (liftM Just act) (const $ return Nothing)
src/HsDev/Server/Commands.hs view
@@ -21,6 +21,7 @@ import Control.Monad.Error 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.Either (isLeft) @@ -29,7 +30,9 @@ import Data.Monoid import Data.Traversable (traverse) import Network.Socket hiding (connect) -import qualified Network.Socket as Net +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.IO @@ -40,6 +43,7 @@ import qualified Control.Concurrent.FiniteChan as F import Data.Lisp import System.Console.Cmd hiding (run) +import Text.Format ((~~), (%)) import qualified HsDev.Cache.Structured as SC import qualified HsDev.Client.Commands as Client @@ -59,7 +63,7 @@ import System.Process import System.Win32.FileMapping.Memory (withMapFile, readMapFile) import System.Win32.FileMapping.NamePool -import System.Win32.PowerShell (translateArg) +import System.Win32.PowerShell (escape, quote, quoteDouble) #else import System.Posix.Process import System.Posix.IO @@ -87,17 +91,26 @@ let args = ["run"] ++ toArgs (Args [] 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 $ $ -WindowStyle Hidden -WorkingDirectory $ } catch { $$_.Exception, $$_.InvocationInfo.Line }" ~~ ( + escape quote myExe % + (intercalate ", " (map biescape args)) % + escape quote curDir) r <- readProcess "powershell" [ "-Command", - unwords [ - "&", "{", "start-process", - translateArg myExe, - intercalate ", " (map translateArg args), - "-WindowStyle Hidden", - "}"]] "" + script] "" if all isSpace r then putStrLn $ "Server started at port " ++ (fromJust $ arg "port" sopts) - else putStrLn $ "Failed to start server: " ++ r + else mapM_ putStrLn [ + "Failed to start server", + "\tCommand: " ++ script, + "\tResult: " ++ r] #else let forkError :: SomeException -> IO () @@ -131,9 +144,8 @@ s <- socket AF_INET Stream defaultProtocol addr' <- inet_addr "127.0.0.1" Net.connect s $ SockAddrInet (fromIntegral $ fromJust $ iarg "port" sopts) addr' - bracket (socketToHandle s ReadWriteMode) hClose $ \h -> - processClientHandle s h (copts { - commandExit = killThread me }) + flip finally (close s) $ processClientSocket s (copts { + commandExit = killThread me }) | otherwise = runServer sopts $ \copts -> do commandLog copts $ "Server started at port " ++ fromJust (arg "port" sopts) @@ -155,23 +167,23 @@ forever $ logIO "accept client exception: " (commandLog copts) $ do s' <- fst <$> accept s void $ forkIO $ logIO (show s' ++ " exception: ") (commandLog copts) $ - bracket (socketToHandle s' ReadWriteMode) hClose $ \h -> - bracket newEmptyMVar (`putMVar` ()) $ \done -> do - me <- myThreadId - let - timeoutWait = do - notDone <- isEmptyMVar done - when notDone $ do - void $ forkIO $ do - threadDelay 1000000 - void $ tryPutMVar done () - killThread me - takeMVar done - waitForever = forever $ hGetLineBS h - F.putChan clientChan timeoutWait - processClientHandle s' h (copts { - commandHold = waitForever, - commandExit = serverStop }) + flip finally (close s') $ + bracket newEmptyMVar (`putMVar` ()) $ \done -> do + me <- myThreadId + let + timeoutWait = do + notDone <- isEmptyMVar done + when notDone $ do + void $ forkIO $ do + threadDelay 1000000 + void $ tryPutMVar done () + killThread me + takeMVar done + -- waitForever = forever $ hGetLineBS h + F.putChan clientChan timeoutWait + processClientSocket s' (copts { + -- commandHold = waitForever, + commandExit = serverStop }) takeMVar waitListen DB.readAsync (commandDatabase copts) >>= writeCache sopts (commandLog copts) @@ -283,14 +295,14 @@ s <- socket AF_INET Stream defaultProtocol addr' <- inet_addr "127.0.0.1" Net.connect s (SockAddrInet (fromIntegral $ fromJust $ iarg "port" copts) addr') - h <- socketToHandle s ReadWriteMode - L.hPutStrLn h $ encode $ Message Nothing $ reqCall `M.withOpts` [ - "current-directory" %-- curDir, - "data" %-? (fromUtf8 . encode <$> stdinData), - "timeout" %-? (iarg "timeout" copts :: Maybe Integer), - if flagSet "silent" copts then hoist "silent" else mempty] - hFlush h - peekResponse h + bracket (socketToHandle s ReadWriteMode) hClose $ \h -> do + L.hPutStrLn h $ encode $ Message Nothing $ reqCall `M.withOpts` [ + "current-directory" %-- curDir, + "data" %-? (fromUtf8 . encode <$> stdinData), + "timeout" %-? (iarg "timeout" copts :: Maybe Integer), + if flagSet "silent" copts then hoist "silent" else mempty] + hFlush h + peekResponse h peekResponse h = do resp <- hGetLineBS h @@ -452,10 +464,38 @@ commandLog copts $ name ++ " disconnected" join $ takeMVar var +{- -- | Process client by Handle processClientHandle :: Show a => a -> Handle -> CommandOptions -> IO () processClientHandle n h = processClient (show n) (hGetLineBS h) (\s -> L.hPutStrLn h s >> hFlush h) +-} +-- | Process client by socket +processClientSocket :: Socket -> CommandOptions -> IO () +processClientSocket s copts = do + recvChan <- F.newChan + void $ forkIO $ finally + (Net.getContents s >>= mapM_ (F.putChan recvChan) . L.lines) + (F.closeChan recvChan) + processClient (show s) (getChan_ recvChan) (sendLine s) (copts { + commandHold = forever (getChan_ recvChan) }) + where + getChan_ :: F.Chan a -> IO a + getChan_ = F.getChan >=> maybe noData return + noData :: IO a + noData = throwIO $ userError "Receive chan closed" + -- 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) + -- | Perform action on cache withCache :: Opts String -> a -> (FilePath -> IO a) -> IO a withCache sopts v onCache = case arg "cache" sopts of @@ -466,7 +506,14 @@ writeCache sopts logMsg' d = withCache sopts () $ \cdir -> do logMsg' $ "writing cache to " ++ cdir logIO "cache writing exception: " logMsg' $ do - SC.dump cdir $ structurize d + let + sd = structurize d + SC.dump cdir sd + forM_ (M.keys (structuredCabals sd)) $ \c -> logMsg' ("cache write: cabal " ++ show c) + forM_ (M.keys (structuredProjects sd)) $ \p -> logMsg' ("cache write: project " ++ p) + case allModules (structuredFiles sd) of + [] -> return () + ms -> logMsg' $ "cache write: " ++ show (length ms) ++ " files" logMsg' $ "cache saved to " ++ cdir readCache :: Opts String -> (String -> IO ()) -> (FilePath -> ErrorT String IO Structured) -> IO (Maybe Database)
src/HsDev/Server/Message.hs view
@@ -5,8 +5,8 @@ messagesById, Request(..), requestToArgs, withOpts, withoutOpts, - Notification(..), Result(..), - Response, isNotification, notification, result, responseError, + Notification(..), Result(..), ResultPart(..), + Response, isNotification, notification, result, responseError, resultPart, groupResponses, responsesById ) where @@ -39,7 +39,7 @@ instance FromJSON a => FromJSON (Message a) where parseJSON = withObject "message" $ \v -> - Message <$> (fmap join (v .::? "id")) <*> parseJSON (Object v) + Message <$> fmap join (v .::? "id") <*> parseJSON (Object v) instance Foldable Message where foldMap f (Message _ m) = f m @@ -110,6 +110,15 @@ Result <$> v .:: "result", Error <$> v .:: "error" <*> v .:: "details"] +-- | 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" + type Response = Either Notification Result isNotification :: Response -> Bool @@ -123,6 +132,9 @@ responseError :: String -> [Pair] -> Response responseError e ds = Right $ Error e $ M.fromList $ map (first unpack) ds + +resultPart :: ToJSON a => a -> Notification +resultPart = Notification . toJSON . ResultPart . toJSON instance ToJSON Response where toJSON (Left n) = toJSON n
src/HsDev/Server/Types.hs view
@@ -8,6 +8,7 @@ import Control.Applicative import Control.Monad.Error +import Control.Monad.Reader import Data.Aeson hiding (Result, Error) import Data.Aeson.Types (Pair) import qualified Data.HashMap.Strict as HM (null) @@ -18,7 +19,7 @@ import HsDev.Project import HsDev.Symbols import HsDev.Server.Message -import HsDev.Tools.GhcMod (OutputMessage, TypedRegion, GhcModT) +import HsDev.Tools.GhcMod (OutputMessage, TypedRegion, WorkerMap) import HsDev.Tools.Ghc.Worker (Worker, Ghc) #if mingw32_HOST_OS @@ -36,8 +37,8 @@ #if mingw32_HOST_OS commandMmapPool :: Maybe Pool, #endif - commandGhc :: Worker (Ghc ()), - commandGhcMod :: Worker (FilePath, GhcModT IO ()), + commandGhc :: Worker Ghc, + commandGhcMod :: Worker (ReaderT WorkerMap IO), commandNotify :: Notification -> IO (), commandLink :: IO (), commandHold :: IO (),
src/HsDev/Symbols.hs view
@@ -3,11 +3,16 @@ module HsDev.Symbols ( -- * Information - Import(..), + Export(..), export, + ImportList(..), passImportList, + Import(..), importName, import_, Symbol(..), - ModuleId(..), Module(..), moduleLocals, moduleLocalDeclarations, moduleModuleDeclarations, moduleId, + ModuleId(..), unnamedModuleId, + Module(..), sortDeclarations, moduleLocals, + setDefinedIn, dropExternals, clearDefinedIn, + moduleLocalDeclarations, moduleModuleDeclarations, moduleId, Locals(..), - Declaration(..), declarationLocals, + Declaration(..), decl, definedIn, declarationLocals, scopes, TypeInfo(..), DeclarationInfo(..), ModuleDeclaration(..), @@ -24,6 +29,8 @@ Canonicalize(..), locateProject, locateSourceDir, + sourceModuleRoot, + importedModulePath, -- * Modifiers addDeclaration, @@ -43,12 +50,13 @@ import Control.Monad.Error import Data.Aeson import Data.List -import Data.Map (Map) import Data.Maybe (fromMaybe) -import qualified Data.Map as M import Data.Monoid (Monoid(mempty)) +import Data.Ord (comparing) import Data.Time.Clock.POSIX (POSIXTime) import Data.Foldable (Foldable(..)) +import Data.Text (Text, unpack) +import qualified Data.Text as T (concat, split, unpack) import Data.Traversable (Traversable(..)) import System.Directory import System.FilePath @@ -58,25 +66,88 @@ import HsDev.Project import HsDev.Util (tab, tabs, (.::)) +-- | Module export +data Export = ExportName (Maybe Text) Text | ExportModule Text + deriving (Eq, Ord) + +instance NFData Export where + rnf (ExportName q n) = rnf q `seq` rnf n + rnf (ExportModule m) = rnf m + +instance Show Export where + show (ExportName Nothing n) = unpack n + show (ExportName (Just q) n) = unpack q ++ "." ++ unpack n + show (ExportModule m) = "module " ++ unpack m + +instance ToJSON Export where + toJSON (ExportName q n) = object ["module" .= q, "name" .= n] + toJSON (ExportModule m) = object ["module" .= m] + +instance FromJSON Export where + parseJSON = withObject "export" $ \v -> + (ExportName <$> (v .:: "module") <*> (v .:: "name")) <|> + (ExportModule <$> (v .:: "module")) + +-- | 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 + +-- | Import list +data ImportList = ImportList { + hidingList :: Bool, + importSpec :: [Text] } + 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 unpack ls) ++ ")" + +instance ToJSON ImportList where + toJSON (ImportList h ls) = object [ + "hiding" .= h, + "spec" .= ls] + +instance FromJSON ImportList where + parseJSON = withObject "import-list" $ \v -> ImportList <$> + v .:: "hiding" <*> + v .:: "spec" + +-- | Check whether name pass import list +passImportList :: ImportList -> Text -> Bool +passImportList (ImportList hiding names) n + | hiding = n `notElem` names + | otherwise = n `elem` names + -- | Module import data Import = Import { - importModuleName :: String, + importModuleName :: Text, importIsQualified :: Bool, - importAs :: Maybe String, + importAs :: Maybe Text, + importList :: Maybe ImportList, importPosition :: Maybe Position } deriving (Eq, Ord) instance NFData Import where - rnf (Import m q a l) = rnf m `seq` rnf q `seq` rnf a `seq` rnf l + 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 = "import " ++ (if importIsQualified i then "qualified " else "") ++ importModuleName i ++ maybe "" (" as " ++) (importAs i) + 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 [ "name" .= importModuleName i, "qualified" .= importIsQualified i, "as" .= importAs i, + "import-list" .= importList i, "pos" .= importPosition i] instance FromJSON Import where @@ -84,10 +155,19 @@ v .:: "name" <*> v .:: "qualified" <*> v .:: "as" <*> + v .:: "import-list" <*> v .:: "pos" +-- | Get import module name +importName :: Import -> Text +importName i = fromMaybe (importModuleName i) $ importAs i + +-- | Simple import +import_ :: Text -> Import +import_ n = Import n False Nothing Nothing Nothing + -- | Imported module can be accessed via qualifier -importQualifier :: Maybe String -> Import -> Bool +importQualifier :: Maybe Text -> Import -> Bool importQualifier Nothing i | not (importIsQualified i) = True | otherwise = False @@ -123,7 +203,7 @@ -- | Module id data ModuleId = ModuleId { - moduleIdName :: String, + moduleIdName :: Text, moduleIdLocation :: ModuleLocation } deriving (Eq, Ord) @@ -131,7 +211,7 @@ rnf (ModuleId n l) = rnf n `seq` rnf l instance Show ModuleId where - show (ModuleId n l) = "module " ++ n ++ " from " ++ show l + show (ModuleId n l) = "module " ++ unpack n ++ " from " ++ show l instance ToJSON ModuleId where toJSON m = object [ @@ -143,14 +223,17 @@ v .:: "name" <*> v .:: "location" +unnamedModuleId :: ModuleLocation -> ModuleId +unnamedModuleId = ModuleId "" + -- | Module data Module = Module { - moduleName :: String, - moduleDocs :: Maybe String, + moduleName :: Text, + moduleDocs :: Maybe Text, moduleLocation :: ModuleLocation, - moduleExports :: [String], + moduleExports :: Maybe [Export], moduleImports :: [Import], - moduleDeclarations :: Map String Declaration } + moduleDeclarations :: [Declaration] } deriving (Ord) instance ToJSON Module where @@ -160,7 +243,7 @@ "location" .= moduleLocation m, "exports" .= moduleExports m, "imports" .= moduleImports m, - "declarations" .= M.elems (moduleDeclarations m)] + "declarations" .= moduleDeclarations m] instance FromJSON Module where parseJSON = withObject "module" $ \v -> Module <$> @@ -169,7 +252,7 @@ v .:: "location" <*> v .:: "exports" <*> v .:: "imports" <*> - ((M.fromList . map (declarationName &&& id)) <$>v .:: "declarations") + 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 @@ -179,26 +262,42 @@ instance Show Module where show m = unlines $ filter (not . null) [ - "module " ++ moduleName m, + "module " ++ unpack (moduleName m), "\tlocation: " ++ show (moduleLocation m), - "\texports: " ++ intercalate ", " (moduleExports m), + "\texports: " ++ maybe "*" (intercalate ", " . map show) (moduleExports m), "\timports:", unlines $ map (tab 2 . show) $ moduleImports m, "\tdeclarations:", - unlines $ map (tabs 2 . show) $ M.elems (moduleDeclarations m), - maybe "" ("\tdocs: " ++) (moduleDocs m)] + unlines $ map (tabs 2 . show) $ moduleDeclarations m, + maybe "" (("\tdocs: " ++) . unpack) (moduleDocs m)] +sortDeclarations :: [Declaration] -> [Declaration] +sortDeclarations = sortBy (comparing declarationName) + -- | Bring locals to top moduleLocals :: Module -> Module moduleLocals m = m { moduleDeclarations = moduleLocalDeclarations m } +-- | Set all declaration `definedIn` to this module +setDefinedIn :: Module -> Module +setDefinedIn m = m { + moduleDeclarations = map (`definedIn` moduleId m) (moduleDeclarations m) } + +-- | Drop all declarations, that not defined in this module +dropExternals :: Module -> Module +dropExternals m = m { + moduleDeclarations = filter ((/= Just (moduleId m)) . declarationDefined) (moduleDeclarations m) } + +-- | Clear `definedIn` information +clearDefinedIn :: Module -> Module +clearDefinedIn m = m { + moduleDeclarations = map (\d -> d { declarationDefined = Nothing }) (moduleDeclarations m) } + -- | Get declarations with locals -moduleLocalDeclarations :: Module -> Map String Declaration +moduleLocalDeclarations :: Module -> [Declaration] moduleLocalDeclarations = - M.fromList . - map (declarationName &&& id) . + sortDeclarations . concatMap declarationLocals' . - M.elems . moduleDeclarations where declarationLocals' :: Declaration -> [Declaration] @@ -206,7 +305,7 @@ -- | Get list of declarations as ModuleDeclaration moduleModuleDeclarations :: Module -> [ModuleDeclaration] -moduleModuleDeclarations m = [ModuleDeclaration (moduleId m) d | d <- M.elems (moduleDeclarations m)] +moduleModuleDeclarations m = [ModuleDeclaration (moduleId m) d | d <- moduleDeclarations m] -- Make ModuleId by Module moduleId :: Module -> ModuleId @@ -220,24 +319,29 @@ -- | Declaration data Declaration = Declaration { - declarationName :: String, - declarationDocs :: Maybe String, + 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 d l x) = rnf n `seq` rnf d `seq` rnf l `seq` rnf x + 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: " ++) $ declarationDocs d, + maybe "" (("\tdocs: " ++) . unpack) $ declarationDocs d, + maybe "" (("\tdefined in: " ++) . show) $ declarationDefined d, maybe "" (("\tlocation: " ++ ) . show) $ declarationPosition d] instance ToJSON Declaration where toJSON d = object [ "name" .= declarationName d, + "defined" .= declarationDefined d, + "imported" .= declarationImported d, "docs" .= declarationDocs d, "pos" .= declarationPosition d, "decl" .= declaration d] @@ -245,23 +349,39 @@ instance FromJSON Declaration where parseJSON = withObject "declaration" $ \v -> Declaration <$> v .:: "name" <*> + v .:: "defined" <*> + v .:: "imported" <*> v .:: "docs" <*> v .:: "pos" <*> v .:: "decl" instance Locals Declaration where locals = locals . declaration - where_ d ds = d { declaration = where_ (declaration d) ds } + where_ d ds = d { declaration = declaration d `where_` ds } +decl :: Text -> DeclarationInfo -> Declaration +decl n = Declaration n Nothing Nothing Nothing Nothing + +definedIn :: Declaration -> ModuleId -> Declaration +definedIn d m = d { declarationDefined = Just m } + declarationLocals :: Declaration -> [Declaration] declarationLocals d = map prefix' $ locals $ declaration d where - prefix' decl = decl { declarationName = declarationName decl } + prefix' decl' = decl' { declarationName = declarationName decl' } --- | Common info for type/newtype/data/class +-- | Get scopes of @Declaration@, where @Nothing@ is global scope +scopes :: Declaration -> [Maybe Text] +scopes d = globalScope $ map (Just . importName) is where + is = fromMaybe [] $ declarationImported d + globalScope + | any (not . importIsQualified) is = (Nothing :) + | otherwise = id + +-- | Common info for type, newtype, data and class data TypeInfo = TypeInfo { - typeInfoContext :: Maybe String, - typeInfoArgs :: [String], - typeInfoDefinition :: Maybe String } + typeInfoContext :: Maybe Text, + typeInfoArgs :: [Text], + typeInfoDefinition :: Maybe Text } deriving (Eq, Ord, Read, Show) instance NFData TypeInfo where @@ -280,11 +400,16 @@ v .:: "def" showTypeInfo :: TypeInfo -> String -> String -> String -showTypeInfo ti pre name = pre ++ maybe "" (++ " =>") (typeInfoContext ti) ++ " " ++ name ++ " " ++ unwords (typeInfoArgs ti) ++ maybe "" (" = " ++) (typeInfoDefinition ti) +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 String, localDeclarations :: [Declaration] } | + Function { functionType :: Maybe Text, localDeclarations :: [Declaration] } | Type { typeInfo :: TypeInfo } | NewType { newTypeInfo :: TypeInfo } | Data { dataInfo :: TypeInfo } | @@ -325,7 +450,7 @@ where_ d _ = d -- | Get function type of type info -declarationInfo :: DeclarationInfo -> Either (Maybe String, [Declaration]) TypeInfo +declarationInfo :: DeclarationInfo -> Either (Maybe Text, [Declaration]) TypeInfo declarationInfo (Function t ds) = Left (t, ds) declarationInfo (Type ti) = Right ti declarationInfo (NewType ti) = Right ti @@ -375,9 +500,10 @@ v .:: "declaration" -- | Returns qualified name of symbol -qualifiedName :: ModuleId -> Declaration -> String -qualifiedName m d = moduleIdName m ++ "." ++ declarationName d +qualifiedName :: ModuleId -> Declaration -> Text +qualifiedName m d = T.concat [moduleIdName m, ".", declarationName d] +-- | Canonicalize all paths within something class Canonicalize a where canonicalize :: a -> IO a @@ -417,31 +543,48 @@ proj <- MaybeT $ fmap (either (const Nothing) Just) $ runErrorT $ loadProject p MaybeT $ return $ findSourceDir proj file +-- | 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 + -- | Add declaration to module addDeclaration :: Declaration -> Module -> Module -addDeclaration decl m = m { moduleDeclarations = decls' } where - decls' = M.insert (declarationName decl) decl $ moduleDeclarations m +addDeclaration decl' m = m { moduleDeclarations = decls' } where + decls' = sortDeclarations $ decl' : moduleDeclarations m -- | Unalias import name -unalias :: Module -> String -> [String] +unalias :: Module -> Text -> [Text] unalias m alias = [importModuleName i | i <- moduleImports m, importAs i == Just alias] instance Documented ModuleId where - brief m = moduleIdName m ++ " in " ++ show (moduleIdLocation m) + brief m = unpack (moduleIdName m) ++ " in " ++ show (moduleIdLocation m) instance Documented Module where - brief m = moduleName m ++ " in " ++ show (moduleLocation m) + brief m = unpack (moduleName m) ++ " in " ++ show (moduleLocation m) detailed m = unlines $ header ++ docs ++ cts where header = [brief m, ""] - docs = maybe [] return $ moduleDocs 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 "" (" :: " ++) f + Left (f, _) -> name ++ maybe "" ((" :: " ++) . unpack) f Right ti -> showTypeInfo ti (fromMaybe err $ declarationTypeName $ declaration d) name where - name = declarationName d + name = unpack $ declarationName d err = error "Impossible happened: declarationTypeName" instance Documented ModuleDeclaration where @@ -449,8 +592,8 @@ -- | Module contents moduleContents :: Module -> [String] -moduleContents = map showDecl . M.elems . moduleDeclarations where - showDecl d = brief d ++ maybe "" (" -- " ++) (declarationDocs d) +moduleContents = map showDecl . moduleDeclarations where + showDecl d = brief d ++ maybe "" ((" -- " ++) . unpack) (declarationDocs d) -- | Inspection data data Inspection =
src/HsDev/Symbols/Class.hs view
@@ -5,12 +5,14 @@ module HsDev.Symbols.Location ) where +import Data.Text (Text) + import HsDev.Symbols.Location class Symbol a where - symbolName :: a -> String - symbolQualifiedName :: a -> String - symbolDocs :: a -> Maybe String + symbolName :: a -> Text + symbolQualifiedName :: a -> Text + symbolDocs :: a -> Maybe Text symbolLocation :: a -> Location symbolModuleLocation :: Symbol a => a -> ModuleLocation
src/HsDev/Symbols/Documented.hs view
@@ -3,6 +3,8 @@ defaultDetailed ) where +import Data.Text (unpack) + import HsDev.Symbols.Class -- | Documented symbol @@ -15,7 +17,7 @@ defaultDetailed :: Documented a => a -> [String] defaultDetailed s = header ++ docs ++ loc where header = [brief s, ""] - docs = maybe [] return $ symbolDocs s + docs = maybe [] (return . unpack) $ symbolDocs s loc | null mloc = [] | otherwise = ["Defined at " ++ mloc]
src/HsDev/Symbols/Location.hs view
@@ -1,11 +1,12 @@ {-# LANGUAGE OverloadedStrings #-} module HsDev.Symbols.Location ( - ModulePackage(..), ModuleLocation(..), moduleSource, moduleCabalPackage, + ModulePackage(..), ModuleLocation(..), moduleSource, moduleProject_, moduleStandalone, moduleCabal_, moduleCabalPackage, Position(..), Region(..), region, regionLines, regionStr, Location(..), packageOpt, + recalcTabs, module HsDev.Cabal ) where @@ -15,7 +16,7 @@ import Control.Monad (join) import Data.Aeson import Data.Char (isSpace, isDigit) -import Data.List (intercalate) +import Data.List (intercalate, findIndex) import Data.Maybe import Text.Read (readMaybe) @@ -67,6 +68,18 @@ moduleSource (FileModule f _) = Just f moduleSource _ = Nothing +moduleProject_ :: ModuleLocation -> Maybe Project +moduleProject_ (FileModule _ p) = p +moduleProject_ _ = Nothing + +moduleStandalone :: ModuleLocation -> Bool +moduleStandalone (FileModule _ Nothing) = True +moduleStandalone _ = False + +moduleCabal_ :: ModuleLocation -> Maybe Cabal +moduleCabal_ (CabalModule c _ _) = Just c +moduleCabal_ _ = Nothing + moduleCabalPackage :: ModuleLocation -> Maybe ModulePackage moduleCabalPackage (CabalModule _ p _) = p moduleCabalPackage _ = Nothing @@ -88,8 +101,8 @@ instance FromJSON ModuleLocation where parseJSON = withObject "module location" $ \v -> - (FileModule <$> v .:: "file" <*> ((fmap project) <$> (v .:: "project"))) <|> - (CabalModule <$> v .:: "cabal" <*> (fmap (join . fmap readMaybe) (v .:: "package")) <*> v .:: "name") <|> + (FileModule <$> v .:: "file" <*> (fmap project <$> (v .:: "project"))) <|> + (CabalModule <$> v .:: "cabal" <*> fmap (join . fmap readMaybe) (v .:: "package") <*> v .:: "name") <|> (ModuleSource <$> v .:: "source") data Position = Position { @@ -174,3 +187,17 @@ packageOpt :: Maybe ModulePackage -> [String] packageOpt = maybeToList . fmap (("-package " ++) . packageName) + +-- | Recalc position to interpret '\t' as one symbol instead of 8 +recalcTabs :: String -> Position -> Position +recalcTabs cts (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' = 8 + charSize _ = 1
+ src/HsDev/Symbols/Resolve.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-} + +module HsDev.Symbols.Resolve ( + ResolveM(..), ResolvedTree, ResolvedModule(..), scopeModule, exportsModule, resolvedTopScope, + resolve, resolveOne, resolveModule, exported, resolveImport, + mergeImported + ) where + +import Control.Applicative +import Control.Arrow +import Control.Monad.Reader +import Control.Monad.State +import Data.Foldable (Foldable) +import Data.Function (on) +import Data.List (sortBy, groupBy, find) +import Data.Map (Map) +import qualified Data.Map as M +import Data.Maybe (fromMaybe, maybeToList) +import Data.Monoid (mconcat, mappend) +import Data.Ord (comparing) +import Data.String (fromString) +import Data.Text (Text) +import Data.Traversable (Traversable, traverse) + +import HsDev.Database +import HsDev.Project +import HsDev.Symbols +import HsDev.Symbols.Util + +-- | Resolve monad uses existing @Database@ and @ResolvedTree@ as state. +newtype ResolveM a = ResolveM { runResolveM :: ReaderT Database (State ResolvedTree) a } + deriving (Functor, Applicative, Monad, MonadState ResolvedTree, MonadReader Database) + +-- | 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] } + +-- | Make @Module@ with scope declarations +scopeModule :: ResolvedModule -> Module +scopeModule r = (resolvedModule r) { moduleDeclarations = resolvedScope r } + +-- | Make @Module@ with exported only declarations +exportsModule :: ResolvedModule -> Module +exportsModule r = (resolvedModule r) { moduleDeclarations = resolvedExports r } + +-- | Get top-level scope +resolvedTopScope :: ResolvedModule -> [Declaration] +resolvedTopScope = filter isTop . resolvedScope where + isTop :: Declaration -> Bool + isTop = any (not . importIsQualified) . fromMaybe [] . declarationImported + +-- | Resolve modules, function is not IO, so all file names must be canonicalized +resolve :: (Traversable t, Foldable t) => Database -> t Module -> t ResolvedModule +resolve db = flip evalState M.empty . flip runReaderT db . runResolveM . traverse resolveModule + +-- | 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 $ moduleId m) >>= maybe resolveModule' return where + resolveModule' = save $ case moduleLocation m of + CabalModule {} -> return ResolvedModule { + resolvedModule = m, + resolvedScope = moduleDeclarations m, + resolvedExports = moduleDeclarations m } + _ -> do + scope' <- + liftM ((thisDecls ++) . mergeImported . concat) . + mapM (resolveImport m) . + (import_ (fromString "Prelude") :) . + moduleImports $ m + let + exports' = + concatMap (exported scope') . + fromMaybe [] . + moduleExports $ m + return $ ResolvedModule m (sortDeclarations scope') (sortDeclarations exports') + thisDecls :: [Declaration] + thisDecls = map selfImport $ moduleDeclarations m + selfImport :: Declaration -> Declaration + selfImport d = d { declarationImported = Just [import_ $ moduleName m] } + save :: ResolveM ResolvedModule -> ResolveM ResolvedModule + save act = do + rm <- act + modify $ M.insert (moduleId (resolvedModule rm)) rm + return rm + +-- | Select declarations exported with @Export@ +exported :: [Declaration] -> Export -> [Declaration] +exported ds (ExportName q n) = maybeToList $ find isExported ds where + isExported :: Declaration -> Bool + isExported decl' = declarationName decl' == n && case q of + Nothing -> any (not . importIsQualified) $ fromMaybe [] $ declarationImported decl' + Just q' -> any ((== q') . importName) $ fromMaybe [] $ declarationImported decl' +exported ds (ExportModule m) = + filter (any (unqualBy m) . fromMaybe [] . declarationImported) ds + where + unqualBy :: Text -> Import -> Bool + unqualBy m' i = importName i == m' && not (importIsQualified i) + +-- | Bring declarations into scope +resolveImport :: Module -> Import -> ResolveM [Declaration] +resolveImport m i = liftM (map $ setImport i) resolveImport' where + resolveImport' :: ResolveM [Declaration] + resolveImport' = do + ms <- case moduleLocation m of + FileModule file proj -> do + db <- ask + let + proj' = proj >>= refineProject db + case proj' of + Nothing -> selectImport i [ + inFile $ importedModulePath (moduleName m) file (importModuleName i), + byCabal] + Just p -> selectImport i [ + inProject p, + inDepsOf' file p] + CabalModule cabal _ _ -> selectImport i [inCabal cabal] + ModuleSource _ -> selectImport i [byCabal] + liftM (filterImportList . concatMap resolvedExports) $ mapM resolveModule ms + setImport :: Import -> Declaration -> Declaration + setImport i' d' = d' { declarationImported = Just [i'] `mappend` declarationImported d' } + selectImport :: Import -> [ModuleId -> Bool] -> ResolveM [Module] + selectImport i' fs = liftM (selectModules select') ask where + select' md = moduleName md == importModuleName i' && any ($ moduleId md) (byImport i' : fs) + filterImportList :: [Declaration] -> [Declaration] + filterImportList = case importList i of + Nothing -> id + Just il -> filter (passImportList il . declarationName) + byImport :: Import -> ModuleId -> Bool + byImport i' m' = importModuleName i' == moduleIdName m' + deps f p = maybe [] infoDepends $ fileTarget p f + inDepsOf' f p m' = any (`inPackage` m') (deps f p) + +-- | Merge imported declarations +mergeImported :: [Declaration] -> [Declaration] +mergeImported = + map merge' . + groupBy ((==) `on` declId) . + sortBy (comparing declId) + where + declId :: Declaration -> (Text, Maybe ModuleId) + declId = declarationName &&& declarationDefined + merge' :: [Declaration] -> Declaration + merge' [] = error "mergeImported: impossible" + merge' ds@(d:_) = d { declarationImported = mconcat $ map declarationImported ds }
src/HsDev/Symbols/Util.hs view
@@ -1,6 +1,6 @@ module HsDev.Symbols.Util ( - packageOf, projectOf, - inProject, inCabal, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, byCabal, standalone, + projectOf, cabalOf, packageOf, + inProject, inDepsOf, inCabal, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, byCabal, standalone, imports, qualifier, imported, visible, inScope, newestPackage, sourceModule, visibleModule, preferredModule, uniqueModules, @@ -12,6 +12,7 @@ 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 @@ -23,6 +24,12 @@ FileModule _ proj -> proj _ -> Nothing +-- | Get module cabal +cabalOf :: ModuleId -> Maybe Cabal +cabalOf m = case moduleIdLocation m of + CabalModule c _ _ -> Just c + _ -> Nothing + -- | Get module package packageOf :: ModuleId -> Maybe ModulePackage packageOf m = case moduleIdLocation m of @@ -33,6 +40,10 @@ inProject :: Project -> ModuleId -> Bool inProject p m = projectOf m == Just p +-- | Check if module in deps of project target +inDepsOf :: Info -> ModuleId -> Bool +inDepsOf i m = any (`inPackage` m) $ infoDepends i + -- | Check if module in cabal inCabal :: Cabal -> ModuleId -> Bool inCabal c m = case moduleIdLocation m of @@ -64,7 +75,7 @@ -- | Check if declaration is in module inModule :: String -> ModuleId -> Bool -inModule mname m = mname == moduleIdName m +inModule mname m = fromString mname == moduleIdName m -- | Check if module defined in file byFile :: ModuleId -> Bool @@ -90,7 +101,10 @@ -- | Get list of imports, which can be accessed with specified qualifier or unqualified qualifier :: Module -> Maybe String -> [Import] -qualifier m q = filter (importQualifier q) $ (Import "Prelude" False Nothing Nothing : Import (moduleName m) False Nothing Nothing : imports m) +qualifier m q = filter (importQualifier (fmap fromString q)) $ + import_ (fromString "Prelude") : + import_ (moduleName m) : + imports m -- | Check if module imported via imports specified imported :: ModuleId -> [Import] -> Bool
src/HsDev/Tools/Base.hs view
@@ -2,7 +2,7 @@ Result, ToolM, runWait, runWait_, tool, tool_, - match, + matchRx, splitRx, replaceRx, at, inspect, -- * Read parse utils @@ -12,10 +12,12 @@ import Control.Monad.Error 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.RegexPR (matchRegexPR) +import Text.Regex.PCRE ((=~), MatchResult(..)) import HsDev.Symbols import HsDev.Util (liftIOErrors) @@ -41,10 +43,24 @@ tool_ :: FilePath -> [String] -> ToolM String tool_ name args = tool name args "" -match :: String -> String -> Maybe (Int -> Maybe String) -match pat str = do - (_, groups) <- matchRegexPR pat str - return $ \i -> lookup i groups +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
src/HsDev/Tools/Ghc/Prelude.hs view
@@ -4,7 +4,8 @@ ) where import Data.Char (isSpace) -import Text.RegexPR +import Data.List (intercalate, unfoldr) +import Text.Regex.PCRE reduce :: ([a] -> a) -> [a] -> [a] reduce = (return .) @@ -17,7 +18,17 @@ p = reverse . dropWhile isSpace rx :: String -> String -> Maybe String -rx r = fmap (fst . fst) . matchRegexPR r +rx r s = case r =~ s of + "" -> Nothing + res -> Just res srx :: String -> String -> String -> String -srx = gsubRegexPR +srx r w = intercalate w . splitRx r + +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)
src/HsDev/Tools/Ghc/Worker.hs view
@@ -1,29 +1,26 @@+{-# OPTIONS_GHC -fno-warn-orphans #-} + module HsDev.Tools.Ghc.Worker ( ghcWorker, - waitGhc, evaluate, - try, Ghc, module Control.Concurrent.Worker ) where -import Control.Arrow (left) -import Control.Concurrent -import Control.Exception (SomeException) import Control.Monad import Control.Monad.Error +import Control.Monad.Catch import Data.Dynamic -import Exception (gtry) import GHC import GHC.Paths import Packages import Control.Concurrent.Worker -ghcWorker :: IO (Worker (Ghc ())) -ghcWorker = worker_ (runGhc (Just libdir)) ghcInit try where +ghcWorker :: IO (Worker Ghc) +ghcWorker = startWorker (runGhc (Just libdir)) ghcInit where ghcInit f = do fs <- getSessionDynFlags defaultCleanupHandler fs $ do @@ -39,15 +36,12 @@ startMods :: [String] startMods = ["Prelude", "Data.List", "Control.Monad", "HsDev.Tools.Ghc.Prelude"] -waitGhc :: Worker (Ghc ()) -> Ghc a -> ErrorT String IO a -waitGhc w act = ErrorT $ do - var <- newEmptyMVar - sendWork w $ try act >>= liftIO . putMVar var - takeMVar var - evaluate :: String -> Ghc String evaluate expr = liftM fromDynamic (dynCompileExpr $ "show (" ++ expr ++ ")") >>= maybe (fail "evaluate fail") return -try :: Ghc a -> Ghc (Either String a) -try = liftM (left (show :: SomeException -> String)) . gtry +instance MonadThrow Ghc where + throwM = liftIO . throwM + +instance MonadCatch Ghc where + catch = gcatch
src/HsDev/Tools/GhcMod.hs view
@@ -1,8 +1,10 @@-{-# LANGUAGE OverloadedStrings, ConstraintKinds, FlexibleContexts, LambdaCase #-} +{-# LANGUAGE OverloadedStrings, ConstraintKinds, FlexibleContexts, LambdaCase, FlexibleInstances #-} +{-# OPTIONS_GHC -fno-warn-orphans #-} module HsDev.Tools.GhcMod ( list, browse, browseInspection, + langs, flags, info, TypedRegion(..), typeOf, @@ -14,8 +16,8 @@ locateGhcModEnv, ghcModEnvPath, ghcModWorker, - ghcModMultiWorker, - waitGhcMod, + WorkerMap, + ghcModMultiWorker, dispatch, waitMultiGhcMod, GhcModT, @@ -24,16 +26,19 @@ import Control.Applicative import Control.Arrow -import Control.Concurrent (newEmptyMVar, putMVar, takeMVar, newMVar, modifyMVar_) +import Control.Concurrent import Control.DeepSeq -import Control.Exception (SomeException, bracket) +import Control.Exception (SomeException(..)) import Control.Monad.Error -import Control.Monad.CatchIO (MonadCatchIO) +import Control.Monad.Catch (MonadThrow(..), MonadCatch(..)) +import Control.Monad.Reader import Data.Aeson import Data.Char +import Data.List (nub, sort) import Data.Maybe import qualified Data.Map as M -import Exception (gtry) +import Data.String (fromString) +import Exception (gcatch) import GHC (getSessionDynFlags, defaultCleanupHandler) import System.Directory import System.FilePath (normalise) @@ -48,7 +53,7 @@ import HsDev.Project import HsDev.Symbols import HsDev.Tools.Base -import HsDev.Util ((.::), liftIOErrors) +import HsDev.Util ((.::), liftIOErrors, withCurrentDirectory) list :: [String] -> Cabal -> ErrorT String IO [ModuleLocation] list opts cabal = runGhcMod (GhcMod.defaultOptions { GhcMod.ghcUserOptions = opts }) $ do @@ -59,50 +64,86 @@ splitPackage = second (drop 1) . break isSpace browse :: [String] -> Cabal -> String -> Maybe ModulePackage -> ErrorT String IO InspectedModule -browse opts cabal mname mpackage = inspect mloc (return $ browseInspection opts) $ runGhcMod - (GhcMod.defaultOptions { GhcMod.detailed = True, GhcMod.ghcUserOptions = packageOpt mpackage ++ opts }) $ do - ts <- lines <$> GhcMod.browse mpkgname - return $ Module { - moduleName = mname, +browse opts cabal mname mpackage = inspect thisLoc (return $ browseInspection opts) $ runGhcMod + (GhcMod.defaultOptions { GhcMod.detailed = True, GhcMod.qualified = True, GhcMod.ghcUserOptions = packageOpt mpackage ++ opts }) $ do + ds <- (mapMaybe parseDecl . lines) <$> GhcMod.browse mpkgname + return Module { + moduleName = fromString mname, moduleDocs = Nothing, - moduleLocation = mloc, - moduleExports = [], - moduleImports = [], - moduleDeclarations = decls ts } + moduleLocation = thisLoc, + moduleExports = Just $ map (ExportName Nothing . declarationName) ds, + moduleImports = [import_ iname | + iname <- nub (mapMaybe definedModule ds), + iname /= fromString mname], + moduleDeclarations = sortDeclarations ds } where mpkgname = maybe mname (\p -> packageName p ++ ":" ++ mname) mpackage - mloc = CabalModule cabal mpackage mname - decls rs = M.fromList $ map (declarationName &&& id) $ mapMaybe parseDecl rs - parseFunction s = do - groups <- match "(\\w+)\\s+::\\s+(.*)" s - return $ Declaration (groups `at` 1) Nothing Nothing (Function (Just $ groups `at` 2) []) - parseType s = do - groups <- match "(class|type|data|newtype)\\s+(\\w+)(\\s+(\\w+(\\s+\\w+)*))?" s + thisLoc = moduleIdLocation $ mloc mname + mloc mname' = ModuleId (fromString mname') $ CabalModule cabal Nothing mname' + parseDecl s = do + groups <- matchRx rx s let - args = maybe [] words $ groups 3 - return $ Declaration (groups `at` 2) Nothing Nothing (declarationTypeCtor (groups `at` 1) $ TypeInfo Nothing args Nothing) - parseDecl s = parseFunction s `mplus` parseType s + rdecl = decl (fromString $ groups `at` 3) $ case groups 5 of + Nothing -> Function (Just $ fromString $ groups `at` 4) [] + Just k -> declarationTypeCtor k $ + TypeInfo Nothing (maybe [] (map fromString . words) $ groups 7) Nothing + return $ rdecl `definedIn` mloc (init $ groups `at` 1) + definedModule = fmap moduleIdName . declarationDefined + -- groups: + -- 1: "<module>." + -- 3: "<name>" + -- 4: "<type>" or "<rest>" + -- 5: "<kind>" (class, type, data or newtype) + -- 6: "<name>" + -- 7: " <args>" + rx = "^((\\w+\\.)*)(\\w+)\\s+::\\s+((class|type|data|newtype)\\s+(\\w+)((\\s+\\w+)*)?|.*)$" browseInspection :: [String] -> Inspection -browseInspection = InspectionAt 0 +browseInspection = InspectionAt 0 . sort . nub -info :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> String -> GhcModT IO Declaration -info opts cabal file _ _ sname = do +langs :: ErrorT String IO [String] +langs = runGhcMod GhcMod.defaultOptions $ (lines . nullToNL) <$> GhcMod.languages + +flags :: ErrorT String IO [String] +flags = runGhcMod GhcMod.defaultOptions $ (lines . nullToNL) <$> GhcMod.flags + +info :: [String] -> Cabal -> FilePath -> String -> GhcModT IO Declaration +info opts cabal file sname = do + fileCts <- liftIO $ readFile file rs <- withOptions (\o -> o { GhcMod.ghcUserOptions = cabalOpt cabal ++ opts }) $ - GhcMod.info file sname - toDecl rs + liftM nullToNL $ GhcMod.info file sname + toDecl fileCts rs where - toDecl s = maybe (throwError $ strMsg $ "Can't parse info: '" ++ s ++ "'") return $ parseData s `mplus` parseFunction s + toDecl fstr s = + liftM (recalcDeclTabs fstr) . + maybe (throwError $ strMsg $ "Can't parse info: '" ++ sname ++ "'") return $ + parseData s `mplus` parseFunction s + recalcDeclTabs :: String -> Declaration -> Declaration + recalcDeclTabs fstr d = d { declarationPosition = fmap (recalcTabs fstr) (declarationPosition d) } parseFunction s = do - groups <- match (sname ++ "\\s+::\\s+(.*?)(\\s+--(.*))?$") s - return $ Declaration sname Nothing Nothing (Function (Just $ groups `at` 1) []) + groups <- matchRx (sname ++ "\\s+::\\s+(.*?)(\\s+-- Defined (at (.*)|in `(.*)'))?$") s + return (decl (fromString sname) (Function (Just $ fromString $ groups `at` 1) [])) { + declarationDefined = unnamedModuleId <$> + ((groups 4 >>= parseSrc) <|> (mkMod <$> groups 5)), + declarationPosition = groups 4 >>= parsePos } parseData s = do - groups <- match "(newtype|type|data)\\s+((.*)=>\\s+)?(\\S+)\\s+((\\w+\\s+)*)=(\\s*(.*)\\s+-- Defined)?" s + groups <- matchRx "(newtype|type|data)\\s+((.*)=>\\s+)?(\\S+)\\s+((\\w+\\s+)*)=(\\s*(.*)\\s+-- Defined (at (.*)|in `(.*)'))?" s let - args = maybe [] words $ groups 5 - ctx = fmap trim $ groups 3 - def = groups 8 - return $ Declaration sname Nothing Nothing (declarationTypeCtor (groups `at` 1) $ TypeInfo ctx args def) + args = maybe [] (map fromString . words) $ groups 5 + ctx = fmap (fromString . trim) $ groups 3 + def = fmap fromString $ groups 8 + return (decl (fromString sname) (declarationTypeCtor (groups `at` 1) $ TypeInfo ctx args def)) { + declarationDefined = unnamedModuleId <$> + ((groups 10 >>= parseSrc) <|> (mkMod <$> groups 11)), + declarationPosition = groups 10 >>= parsePos } + parseSrc src = case splitRx ":(?=\\d)" src of + [srcFile, _, _] -> Just $ FileModule srcFile Nothing + _ -> Nothing + -- FIXME: Position is returned by ghc-mod and it interprets tab as several spaces instead of one symbol + parsePos src = case splitRx ":(?=\\d)" src of + [_, line, column] -> Position <$> readMaybe line <*> readMaybe column + _ -> Nothing + mkMod = CabalModule cabal Nothing trim = p . p where p = reverse . dropWhile isSpace @@ -127,20 +168,20 @@ v .:: "expr" <*> v .:: "type" -typeOf :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> Int -> Int -> GhcModT IO [TypedRegion] -typeOf opts cabal file _ _ line col = withOptions (\o -> o { GhcMod.ghcUserOptions = cabalOpt cabal ++ opts }) $ do +typeOf :: [String] -> Cabal -> FilePath -> Int -> Int -> GhcModT IO [TypedRegion] +typeOf opts cabal file line col = withOptions (\o -> o { GhcMod.ghcUserOptions = cabalOpt cabal ++ opts }) $ do fileCts <- liftIO $ readFile file ts <- lines <$> GhcMod.types file line col return $ mapMaybe (toRegionType fileCts) ts where toRegionType :: String -> String -> Maybe TypedRegion toRegionType fstr s = do - (r, tp) <- parseRead s $ (,) <$> parseRegion <*> readParse + (r, tp) <- parseRead s $ (,) <$> parseRegion fstr <*> readParse return $ TypedRegion r (regionStr r fstr) tp - parseRegion :: ReadM Region - parseRegion = Region <$> parsePosition <*> parsePosition - parsePosition :: ReadM Position - parsePosition = Position <$> readParse <*> readParse + parseRegion :: String -> ReadM Region + parseRegion fstr = Region <$> parsePosition fstr <*> parsePosition fstr + parsePosition :: String -> ReadM Position + parsePosition fstr = recalcTabs fstr <$> (Position <$> readParse <*> readParse) data OutputMessageLevel = WarningMessage | ErrorMessage deriving (Eq, Ord, Bounded, Enum, Read, Show) @@ -181,18 +222,20 @@ parseOutputMessage :: String -> Maybe OutputMessage parseOutputMessage s = do - groups <- match "^(.+):(\\d+):(\\d+):(\\s*(Warning|Error):)?\\s*(.*)$" s - return $ OutputMessage { + groups <- matchRx "^(.+):(\\d+):(\\d+):(\\s*(Warning|Error):)?\\s*(.*)$" s + return OutputMessage { errorLocation = Location { locationModule = FileModule (normalise (groups `at` 1)) Nothing, locationPosition = Position <$> readMaybe (groups `at` 2) <*> readMaybe (groups `at` 3) }, errorLevel = if groups 5 == Just "Warning" then WarningMessage else ErrorMessage, - errorMessage = map nullToNL (groups `at` 6) } - where - nullToNL = \case - '\0' -> '\n' - ch -> ch + errorMessage = nullToNL (groups `at` 6) } +-- | Replace NULL with newline +nullToNL :: String -> String +nullToNL = map $ \case + '\0' -> '\n' + ch -> ch + check :: [String] -> Cabal -> [FilePath] -> Maybe Project -> GhcModT IO [OutputMessage] check opts cabal files _ = withOptions (\o -> o { GhcMod.ghcUserOptions = cabalOpt cabal ++ opts }) $ do msgs <- lines <$> GhcMod.checkSyntax files @@ -203,7 +246,7 @@ msgs <- lines <$> GhcMod.lint file return $ mapMaybe parseOutputMessage msgs -runGhcMod :: (GhcMod.IOish m, MonadCatchIO m) => GhcMod.Options -> GhcModT m a -> ErrorT String m a +runGhcMod :: (GhcMod.IOish m, MonadCatch m) => GhcMod.Options -> GhcModT m a -> ErrorT String m a runGhcMod opts act = liftIOErrors $ ErrorT $ liftM (left show . fst) $ runGhcModT opts act locateGhcModEnv :: FilePath -> IO (Either Project Cabal) @@ -215,13 +258,14 @@ ghcModEnvPath defaultPath = either projectPath (fromMaybe defaultPath . sandbox) -- | Create ghc-mod worker for project or for sandbox -ghcModWorker :: Either Project Cabal -> IO (Worker (GhcModT IO ())) +ghcModWorker :: Either Project Cabal -> IO (Worker (GhcModT IO)) ghcModWorker p = do home <- getHomeDirectory - worker_ (runGhcModT'' $ ghcModEnvPath home p) id try + startWorker (runGhcModT'' $ ghcModEnvPath home p) id where makeEnv :: FilePath -> IO GhcMod.GhcModEnv makeEnv = GhcMod.newGhcModEnv GhcMod.defaultOptions + -- TODO: Uncomment comment below after ghc-mod exports neccessary functions functionNotExported = True runGhcModT'' :: FilePath -> GhcModT IO () -> IO () runGhcModT'' cur act @@ -234,35 +278,36 @@ defaultCleanupHandler dflags $ do --GhcMod.initializeFlagsWithCradle GhcMod.defaultOptions (GhcMod.gmCradle env') act - withCurrentDirectory :: FilePath -> IO a -> IO a - withCurrentDirectory cur act = bracket getCurrentDirectory setCurrentDirectory $ - const (setCurrentDirectory cur >> act) +type WorkerMap = MVar (M.Map FilePath (Worker (GhcModT IO))) + -- | Manage many ghc-mod workers for each project/sandbox -ghcModMultiWorker :: IO (Worker (FilePath, GhcModT IO ())) -ghcModMultiWorker = worker id initMultiGhcMod multiWork where - initMultiGhcMod f = newMVar M.empty >>= f - multiWork ghcMods (file, act) = do - home <- getHomeDirectory - env' <- locateGhcModEnv file - let - envPath' = ghcModEnvPath home env' - modifyMVar_ ghcMods $ \ghcModsMap -> do - w <- maybe (ghcModWorker env') return $ M.lookup envPath' ghcModsMap - sendWork w act - return $ M.insert envPath' w ghcModsMap +ghcModMultiWorker :: IO (Worker (ReaderT WorkerMap IO)) +ghcModMultiWorker = newMVar M.empty >>= \m -> startWorker (`runReaderT` m) id -waitGhcMod :: Worker (GhcModT IO ()) -> GhcModT IO a -> ErrorT String IO a -waitGhcMod w act = ErrorT $ do - var <- newEmptyMVar - sendWork w $ try act >>= liftIO . putMVar var - takeMVar var +instance MonadThrow (GhcModT IO) where + throwM = lift . throwM -waitMultiGhcMod :: Worker (FilePath, GhcModT IO ()) -> FilePath -> GhcModT IO a -> ErrorT String IO a -waitMultiGhcMod w f act = ErrorT $ do - var <- newEmptyMVar - sendWork w (f, try act >>= liftIO . putMVar var) - takeMVar var +instance MonadCatch (GhcModT IO) where + catch = gcatch -try :: GhcModT IO a -> GhcModT IO (Either String a) -try = liftM (left (show :: SomeException -> String)) . gtry +dispatch :: FilePath -> GhcModT IO a -> ReaderT WorkerMap IO (Task a) +dispatch file act = do + mvar <- ask + home <- liftIO getHomeDirectory + env' <- liftIO $ locateGhcModEnv file + let + envPath' = ghcModEnvPath home env' + liftIO $ modifyMVar mvar $ \wmap -> do + w <- maybe (ghcModWorker env') return $ M.lookup envPath' wmap + t <- pushTask w act + return (M.insert envPath' w wmap, t) + +waitMultiGhcMod :: Worker (ReaderT WorkerMap IO) -> FilePath -> GhcModT IO a -> ErrorT String IO a +waitMultiGhcMod w f = + liftIO . pushTask w . dispatch f >=> + asErrorT . taskWait >=> + asErrorT . taskWait + where + asErrorT :: Monad m => m (Either SomeException a) -> ErrorT String m a + asErrorT = ErrorT . liftM (left (\(SomeException e) -> show e))
src/HsDev/Tools/GhcMod/InferType.hs view
@@ -3,11 +3,14 @@ GhcModT ) where +import Control.Applicative import Control.Monad.Error +import Data.Maybe (listToMaybe) +import Data.String (fromString) +import qualified Data.Text as T (unpack) import Data.Traversable (traverse) import HsDev.Cabal -import HsDev.Project import HsDev.Symbols import HsDev.Tools.GhcMod @@ -17,28 +20,34 @@ untyped _ = False -- | Infer type of declaration -inferType :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> Declaration -> GhcModT IO Declaration -inferType opts cabal src mproj mname decl - | untyped (declaration decl) = infer - | otherwise = return decl +inferType :: [String] -> Cabal -> FilePath -> Declaration -> GhcModT IO Declaration +inferType opts cabal src decl' + | untyped (declaration decl') = infer + | otherwise = return decl' where infer = do - inferred <- liftM declaration $ info opts cabal src mproj mname (declarationName decl) - return decl { - declaration = setType (declaration decl) (getType inferred) } + inferred <- ((getType . declaration) <$> byInfo) <|> byTypeOf + return decl' { + declaration = setType (declaration decl') inferred } + byInfo = info opts cabal src (T.unpack $ declarationName decl') + byTypeOf = case declarationPosition decl' of + Nothing -> fail "No position" + Just (Position l c) -> (fmap typedType . listToMaybe) <$> typeOf opts cabal src l c + setType :: DeclarationInfo -> Maybe String -> DeclarationInfo - setType (Function _ ds) newType = Function newType ds + setType (Function _ ds) newType = Function (fmap fromString newType) ds setType dinfo _ = dinfo getType :: DeclarationInfo -> Maybe String - getType (Function fType _) = fType + getType (Function fType _) = fmap T.unpack fType getType _ = Nothing -- | Infer types for module inferTypes :: [String] -> Cabal -> Module -> GhcModT IO Module inferTypes opts cabal m = case moduleLocation m of - FileModule src p -> do - inferredDecls <- traverse (inferType opts cabal src p (moduleName m)) $ moduleDeclarations m + FileModule src _ -> do + inferredDecls <- traverse (\d -> inferType opts cabal src d <|> return d) $ + moduleDeclarations m return m { moduleDeclarations = inferredDecls } _ -> throwError $ strMsg "Type infer works only for source files"
src/HsDev/Tools/HDocs.hs view
@@ -14,7 +14,8 @@ import qualified Data.ByteString.Lazy.Char8 as L (pack) import Data.Map (Map) import qualified Data.Map as M -import Data.Maybe () +import Data.String (fromString) +import Data.Text (unpack) import System.Process (readProcess) import qualified HDocs.Module as HDocs @@ -34,13 +35,14 @@ -- | Set docs for module setDocs :: Map String String -> Module -> Module -setDocs d m = m { moduleDeclarations = M.mapWithKey setDoc $ moduleDeclarations m } where - setDoc name decl = decl { declarationDocs = M.lookup name d } +setDocs d m = m { moduleDeclarations = map setDoc $ moduleDeclarations m } where + setDoc decl' = decl' { declarationDocs = M.lookup (declarationName decl') d' } + d' = M.mapKeys fromString . M.map fromString $ d -- | Load docs for module loadDocs :: [String] -> Module -> IO Module loadDocs opts m = do - d <- hdocs (moduleName m) opts + d <- hdocs (unpack $ moduleName m) opts return $ setDocs d m hdocsProcess :: String -> [String] -> IO (Maybe (Map String String))
src/HsDev/Tools/Hayoo.hs view
@@ -18,10 +18,11 @@ import qualified Data.ByteString.Lazy.Char8 as L import Data.Either import Network.HTTP -import Text.RegexPR (gsubRegexPR) +import Data.String (fromString) import HsDev.Symbols import HsDev.Symbols.Documented +import HsDev.Tools.Base (replaceRx) import HsDev.Util -- | Hayoo response @@ -56,11 +57,11 @@ ((rights . map hayooValue) <$> (v .:: "result")) instance Symbol HayooSymbol where - symbolName = hayooName - symbolQualifiedName f = case hayooModules f of + symbolName = fromString . hayooName + symbolQualifiedName f = fromString $ case hayooModules f of [] -> hayooName f (m:_) -> m ++ "." ++ hayooName f - symbolDocs = Just . hayooDescription + symbolDocs = Just . fromString . hayooDescription symbolLocation r = Location (ModuleSource $ Just $ resultUri r) Nothing where instance Documented HayooSymbol where @@ -94,11 +95,13 @@ hayooAsDeclaration f | hayooType f `elem` ["function", "type", "newtype", "data", "class"] = Just ModuleDeclaration { declarationModuleId = ModuleId { - moduleIdName = head $ hayooModules f, + moduleIdName = fromString $ head $ hayooModules f, moduleIdLocation = ModuleSource (Just $ resultUri f) }, moduleDeclaration = Declaration { - declarationName = hayooName f, - declarationDocs = Just (addOnline $ untagDescription $ hayooDescription f), + declarationName = fromString $ hayooName f, + declarationDefined = Nothing, + declarationImported = Nothing, + declarationDocs = Just (fromString $ addOnline $ untagDescription $ hayooDescription f), declarationPosition = Nothing, declaration = declInfo } } | otherwise = Nothing @@ -112,7 +115,7 @@ "Hackage URL: " ++ resultUri f] declInfo - | hayooType f == "function" = Function (Just $ hayooSignature f) [] + | hayooType f == "function" = Function (Just $ fromString $ hayooSignature f) [] | hayooType f `elem` ["type", "newtype", "data", "class"] = declarationTypeCtor (hayooType f) $ TypeInfo Nothing [] Nothing | otherwise = error "Impossible" @@ -127,4 +130,4 @@ -- | Remove tags in description untagDescription :: String -> String -untagDescription = gsubRegexPR "</?\\w+[^>]*>" "" +untagDescription = replaceRx "</?\\w+[^>]*>" ""
src/HsDev/Util.hs view
@@ -1,4 +1,5 @@ module HsDev.Util ( + withCurrentDirectory, directoryContents, traverseDirectory, isParent, @@ -14,14 +15,16 @@ -- * UTF-8 fromUtf8, toUtf8, -- * IO - hGetLineBS, logException, logIO, ignoreIO + hGetLineBS, logException, logIO, ignoreIO, + -- * Task + liftTask ) where -import Control.Arrow (second) +import Control.Arrow (second, left) import Control.Exception import Control.Monad import Control.Monad.Error -import qualified Control.Monad.CatchIO as C +import qualified Control.Monad.Catch as C import Data.Aeson import Data.Aeson.Types (Parser) import Data.Char (isSpace) @@ -38,6 +41,13 @@ import System.FilePath import System.IO (Handle) +import Control.Concurrent.Task + +-- | Run action with current directory set +withCurrentDirectory :: FilePath -> IO a -> IO a +withCurrentDirectory cur act = bracket getCurrentDirectory setCurrentDirectory $ + const (setCurrentDirectory cur >> act) + -- | Get directory contents safely directoryContents :: FilePath -> IO [FilePath] directoryContents p = handle ignore $ do @@ -107,17 +117,16 @@ objectUnion _ _ = Null -- | Lift IO exception to ErrorT -liftException :: C.MonadCatchIO m => m a -> ErrorT String m a -liftException act = ErrorT $ C.catch (liftM Right act) onError where - onError = return . Left . (show :: SomeException -> String) +liftException :: C.MonadCatch m => m a -> ErrorT String m a +liftException = ErrorT . liftM (left $ \(SomeException e) -> show e) . C.try -- | Lift IO exception to MonadError -liftExceptionM :: (C.MonadCatchIO m, Error e, MonadError e m) => m a -> m a +liftExceptionM :: (C.MonadCatch m, Error e, MonadError e m) => m a -> m a liftExceptionM act = C.catch act onError where - onError = throwError . strMsg . (show :: SomeException -> String) + onError = throwError . strMsg . (\(SomeException e) -> show e) -- | Lift IO exceptions to ErrorT -liftIOErrors :: C.MonadCatchIO m => ErrorT String m a -> ErrorT String m a +liftIOErrors :: C.MonadCatch m => ErrorT String m a -> ErrorT String m a liftIOErrors act = liftException (runErrorT act) >>= either throwError return eitherT :: (Monad m, Error e, MonadError e m) => Either String a -> m a @@ -144,3 +153,6 @@ ignoreIO :: IO () -> IO () ignoreIO = handle (const (return ()) :: IOException -> IO ()) + +liftTask :: MonadIO m => IO (Task a) -> ErrorT String m a +liftTask = ErrorT . liftM (left (\(SomeException e) -> show e)) . liftIO . join . liftM taskWait
src/System/Console/Cmd.hs view
@@ -9,7 +9,7 @@ import Control.Arrow (Arrow((&&&))) import Control.Monad () -import Data.List (stripPrefix) +import Data.List (stripPrefix, isPrefixOf) import Control.Monad.Error import Data.Map () import Data.Maybe @@ -123,9 +123,15 @@ namedArgs = Opts $ M.delete "help" $ getOpts $ namedArgs a } | otherwise = a onHelp (Args [] _) = Right $ HelpUsage [tool ++ " " ++ brief c | c <- (helpcmd:cmds)] - onHelp (Args cmdname _) = case filter ((cmdname ==) . words . cmdName) (helpcmd:cmds) of - [] -> Left $ "Unknown command: " ++ unwords cmdname + onHelp (Args cmdname _) = case filter ((cmdname `isPrefixOf`) . words . cmdName) (helpcmd:cmds) of + [] -> Left $ unlines $ ("Unknown command: " ++ unwords cmdname) : tryOut helps -> Right $ HelpCommands $ map (cmdName &&& (addHeader . indented)) helps + where + pre = unwords cmdname + maybeCmds = filter (pre `isPrefixOf`) $ map cmdName (helpcmd:cmds) + tryOut = case maybeCmds of + [] -> [] + _ -> "\tMaybe you mean:" : map ("\t\t" ++) maybeCmds addHeader [] = [] addHeader (h:hs) = (tool ++ " " ++ h) : hs
src/System/Win32/FileMapping/Memory.hs view
@@ -3,7 +3,8 @@ withMapFile, readMapFile ) where -import Control.Monad.CatchIO (bracket) +import Control.Arrow (left) +import Control.Monad.Catch import Control.Monad.Cont import Control.Monad.Error import Data.ByteString.Char8 @@ -14,25 +15,25 @@ import System.Win32.Types import System.Win32.Mem -createMap :: Maybe HANDLE -> ProtectFlags -> DDWORD -> Maybe String -> ContT r (ErrorT String IO) HANDLE +createMap :: Maybe HANDLE -> ProtectFlags -> DDWORD -> Maybe String -> ContT r IO HANDLE createMap mh pf sz mn = ContT $ bracket (verify iNVALID_HANDLE_VALUE "Invalid handle" $ - liftIO (createFileMapping mh pf sz mn)) - (liftIO . closeHandle) + createFileMapping mh pf sz mn) + closeHandle -openMap :: FileMapAccess -> Bool -> Maybe String -> ContT r (ErrorT String IO) HANDLE +openMap :: FileMapAccess -> Bool -> Maybe String -> ContT r IO HANDLE openMap f i mn = ContT $ bracket (verify iNVALID_HANDLE_VALUE "Invalid handle" $ - liftIO (openFileMapping f i mn)) - (liftIO . closeHandle) + openFileMapping f i mn) + closeHandle -mapFile :: HANDLE -> FileMapAccess -> DDWORD -> SIZE_T -> ErrorT String IO (Ptr a) -mapFile h f off sz = verify nullPtr "null pointer" $ liftIO (mapViewOfFile h f off sz) +mapFile :: HANDLE -> FileMapAccess -> DDWORD -> SIZE_T -> IO (Ptr a) +mapFile h f off sz = verify nullPtr "null pointer" $ mapViewOfFile h f off sz -- | Write data to named map view of file withMapFile :: String -> ByteString -> IO a -> ErrorT String IO a -withMapFile name str act = flip runContT return $ do - p <- ContT $ \f -> ErrorT (BS.useAsCString str (runErrorT . f)) +withMapFile name str act = liftE $ flip runContT return $ do + p <- ContT $ BS.useAsCString str h <- createMap Nothing pAGE_READWRITE (fromIntegral len) (Just name) ptr <- lift $ mapFile h fILE_MAP_ALL_ACCESS 0 0 liftIO $ do @@ -44,14 +45,17 @@ -- | Read data from named map view of file readMapFile :: String -> ErrorT String IO ByteString -readMapFile name = flip runContT return $ do +readMapFile name = liftE $ flip runContT return $ do h <- openMap fILE_MAP_ALL_ACCESS True (Just name) ptr <- lift $ mapFile h fILE_MAP_ALL_ACCESS 0 0 liftIO $ BS.packCString ptr -verify :: (Error e, MonadError e m, Eq a) => a -> String -> m a -> m a +verify :: Eq a => a -> String -> IO a -> IO a verify v str act = do x <- act if x == v - then throwError (strMsg str) + then ioError (userError str) else return x + +liftE :: MonadCatch m => m a -> ErrorT String m a +liftE = ErrorT . liftM (left (\(SomeException e) -> show e)) . try
src/System/Win32/PowerShell.hs view
@@ -1,4 +1,4 @@-{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances, GeneralizedNewtypeDeriving #-} +{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances, GeneralizedNewtypeDeriving, LambdaCase #-} module System.Win32.PowerShell ( -- * Run PowerShell @@ -9,11 +9,13 @@ PS(..), seqPS, emit, emit_, (=:), invoke, -- * Expression Args(..), CmdLet(..), Expr(..), compile, - raw, name, lit, var, bra, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter, + raw, name, lit, var, bra, cbra, (.=), flag, named, call, cmdlet, lambda, (.|), foreach, filter, -- * Convertible ToPS(..), -- * Escape functions - translate, translateArg + translate, translateArg, + quote, quoteDouble, + escape ) where import Prelude hiding (filter) @@ -23,6 +25,7 @@ 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 @@ -103,16 +106,16 @@ compile (Literal l) = l compile (Var v) = '$':v compile (Bracket e) = "(" ++ compile e ++ ")" -compile (Assign vs es) = intercalate ", " (map ('$':) vs) ++ " = " ++ intercalate ", " (map (compile . bra) es) -compile (Invoke (CmdLet e (Args p ns))) = unwords [invoke', p', ns'] where +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 $ bra e] - p' = intercalate " " $ map (compile . bra) p + _ -> unwords ["&", compile $ cbra e] + p' = intercalate " " $ map (compile . cbra) p ns' = intercalate " " $ concatMap named' $ M.toList ns where named' :: (String, Maybe Expr) -> [String] - named' (n, Just v) = [n, compile $ bra v] - named' (n, Nothing) = [n] + 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 ++ ");" @@ -135,6 +138,11 @@ 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] @@ -181,13 +189,27 @@ toPS = intercalate ", " . map toPS translate :: String -> String -translate s = '"' : 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') +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
src/Text/Format.hs view
@@ -10,8 +10,8 @@ ) where import Control.Arrow (first) -import Data.List (delete, isSuffixOf) -import Text.Regex.Posix +import Data.List (delete, isPrefixOf) +import Text.Regex.PCRE class Format a where format :: a -> String @@ -51,12 +51,14 @@ name = case gs of _:name':_ -> name' _ -> "" - (arg', args') <- split' name - post' <- post %~ args' - return $ concat [ - pre, - if "$" `isSuffixOf` pre then name else arg', - post'] + if null name && "$" `isPrefixOf` post + then do + post' <- tail post %~ hargs + return $ pre ++ "$" ++ post' + else do + (arg', args') <- split' name + post' <- post %~ args' + return $ pre ++ arg' ++ post' where args = hole hargs
tools/Tool.hs view
@@ -31,10 +31,7 @@ hSetBuffering stdout LineBuffering hSetEncoding stdout utf8 hSetEncoding stdin utf8 - as <- getArgs - case as of - [] -> usage name toolCmds - _ -> run toolCmds unknownCmd onError as + getArgs >>= run toolCmds unknownCmd onError where onError :: String -> IO () onError = putStrLn
tools/hsdev.hs view
@@ -5,6 +5,7 @@ ) where import Control.Applicative +import Control.Exception import Control.Monad import Network.Socket (withSocketsDo) import System.Environment (getArgs) @@ -21,7 +22,7 @@ import HsDev.Version main :: IO () -main = withSocketsDo $ do +main = handle logErr' $ withSocketsDo $ do hSetBuffering stdout LineBuffering hSetEncoding stdout utf8 as <- getArgs @@ -30,17 +31,12 @@ exitSuccess let asr = if last as == "-?" then "help" : init as else as - run mainCommands onDef onError asr + run mainCommands (onError "Unknown command") onError asr where onError :: String -> IO () - onError errs = do - putStrLn errs - exitFailure + onError errs = putStrLn errs >> exitFailure - onDef :: IO () - onDef = do - putStrLn "Unknown command" - exitFailure + logErr' (SomeException e) = putStrLn $ "exception " ++ show e mainCommands :: [Cmd (IO ())] mainCommands = withHelp "hsdev" (printWith putStrLn) $ concat [
tools/hsinspect.hs view
@@ -1,10 +1,15 @@-{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings, ViewPatterns #-} module Main ( main ) where +import Control.Applicative +import Control.Monad (liftM) import Control.Monad.IO.Class +import Data.Aeson (toJSON) +import System.FilePath (takeExtension) + import HsDev.Project (readProject) import HsDev.Scan (scanModule) import HsDev.Inspect (inspectContents) @@ -14,22 +19,13 @@ main :: IO () main = toolMain "hsinspect" [ - jsonCmd "module" ["module name"] [ghcOpts] "inspect installed module" inspectModule', - jsonCmd "file" ["source file"] [ghcOpts] "inspect file" inspectFile', - jsonCmd "input" [] [ghcOpts] "inspect input" inspectInput', - jsonCmd_ "cabal" ["project file"] "inspect .cabal file" inspectCabal'] + jsonCmd "" ["what"] [ghcOpts] "depending of what <what> is, inspect installed module, source file (.hs), cabal file (.cabal) or contents, passes as input if no <what> specified" inspect'] where ghcOpts = list "ghc" "option" `short` ['g'] `desc` "options to pass to GHC" ghcs = listArg "ghc" - inspectModule' (Args [mname] opts) = scanModule (ghcs opts) (CabalModule Cabal Nothing mname) - inspectModule' _ = toolError "Specify module name" - - inspectFile' (Args [fname] opts) = scanModule (ghcs opts) (FileModule fname Nothing) - inspectFile' _ = toolError "Specify source file name" - - inspectInput' (Args [] opts) = liftIO getContents >>= inspectContents "stdin" (ghcs opts) - inspectInput' _ = toolError "Invalid arguments" - - inspectCabal' [fcabal] = readProject fcabal - inspectCabal' _ = toolError "Specify project .cabal file" + inspect' (Args [] opts) = liftIO getContents >>= liftM toJSON . inspectContents "stdin" (ghcs opts) + inspect' (Args [fname@(takeExtension -> ".hs")] opts) = toJSON <$> scanModule (ghcs opts) (FileModule fname Nothing) + inspect' (Args [fcabal@(takeExtension -> ".cabal")] _) = toJSON <$> readProject fcabal + inspect' (Args [mname] opts) = toJSON <$> scanModule (ghcs opts) (CabalModule Cabal Nothing mname) + inspect' _ = toolError "Specify module name or file name (.hs or .cabal)"