packages feed

hsdev (empty) → 0.1.0.0

raw patch · 44 files changed

+5579/−0 lines, 44 filesdep +Cabaldep +HTTPdep +MonadCatchIO-transformerssetup-changed

Dependencies added: Cabal, HTTP, MonadCatchIO-transformers, Win32, aeson, aeson-pretty, attoparsec, base, bytestring, containers, deepseq, directory, filepath, ghc, ghc-mod, ghc-paths, haddock, haskell-src-exts, hdocs, hsdev, monad-loops, mtl, network, process, regexpr, text, time, transformers, unix, unordered-containers, vector

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2013, Alexandr `Voidex` Ruchkin
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+    * Neither the name of Alexandr `Voidex` Ruchkin nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ hsdev.cabal view
@@ -0,0 +1,192 @@+-- Initial hsdev.cabal generated by cabal init.  For further documentation,
+--  see http://haskell.org/cabal/users-guide/
+
+name:                hsdev
+version:             0.1.0.0
+synopsis:            Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references etc.
+-- description:
+homepage:            https://github.com/mvoidex/hsdev
+license:             BSD3
+license-file:        LICENSE
+author:              Alexandr `Voidex` Ruchkin
+maintainer:          voidex@live.com
+-- copyright:
+category:            Development
+build-type:          Simple
+cabal-version:       >=1.8
+
+library
+  hs-source-dirs: src
+  ghc-options: -threaded
+  exposed-modules:
+    HsDev
+    HsDev.Cabal
+    HsDev.Cache
+    HsDev.Cache.Structured
+    HsDev.Commands
+    HsDev.Database
+    HsDev.Database.Async
+    HsDev.Inspect
+    HsDev.Project
+    HsDev.Scan
+    HsDev.Scan.Browse
+    HsDev.Symbols
+    HsDev.Symbols.Class
+    HsDev.Symbols.Location
+    HsDev.Symbols.Documented
+    HsDev.Symbols.Util
+    HsDev.Tools.Base
+    HsDev.Tools.Cabal
+    HsDev.Tools.ClearImports
+    HsDev.Tools.GhcMod
+    HsDev.Tools.GhcMod.InferType
+    HsDev.Tools.Hayoo
+    HsDev.Tools.HDocs
+    HsDev.Util
+
+  if os(windows)
+    exposed-modules:
+      System.Win32.PowerShell
+
+  other-modules:
+    Data.Group
+    Data.Async
+
+  build-depends:
+    base >= 4.7 && < 5,
+    aeson >= 0.7.0,
+    aeson-pretty >= 0.7.0,
+    bytestring >= 0.10.0,
+    Cabal >= 1.18.0,
+    containers >= 0.5.0,
+    deepseq >= 1.3.0,
+    directory >= 1.2.0,
+    filepath >= 1.3.0,
+    ghc >= 7.8.1,
+    ghc-mod >= 4.0.0,
+    ghc-paths >= 0.1.0,
+    haddock >= 2.14.0,
+    haskell-src-exts >= 1.14.0,
+    hdocs >= 0.4.0,
+    HTTP >= 4000.2.0,
+    MonadCatchIO-transformers >= 0.3.0,
+    mtl >= 2.1.0,
+    transformers >= 0.3.0,
+    process >= 1.2.0,
+    regexpr >= 0.5.0,
+    time >= 1.4.0,
+    attoparsec >= 0.11.0,
+    unordered-containers >= 0.2.0,
+    text >= 1.1.0
+
+executable hsdev
+  main-is: hsdev.hs
+  hs-source-dirs: tools
+  ghc-options: -threaded
+  other-modules:
+    Control.Concurrent.FiniteChan
+    Commands
+    System.Command
+    Types
+    Update
+
+  if os(windows)
+    other-modules:
+      System.Win32.FileMapping.Memory
+      System.Win32.FileMapping.NamePool
+  else
+    build-depends:
+      unix >= 2.7.0
+
+  build-depends:
+    base >= 4.7 && < 5,
+    hsdev,
+    aeson >= 0.7.0,
+    aeson-pretty >= 0.7.0,
+    bytestring >= 0.10.0,
+    containers >= 0.5.0,
+    deepseq >= 1.3.0,
+    directory >= 1.2.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,
+    text >= 1.1.0,
+    transformers >= 0.3.0,
+    unordered-containers >= 0.2.0,
+    vector >= 0.10.0
+
+  if os(windows)
+    build-depends:
+      Win32 >= 2.3.0
+
+executable hsinspect
+  main-is: hsinspect.hs
+  hs-source-dirs: tools
+  other-modules:
+    System.Command
+    Tool
+  build-depends:
+    base >= 4.7 && < 5,
+    hsdev,
+    aeson >= 0.7.0,
+    aeson-pretty >= 0.7.0,
+    bytestring >= 0.10.0,
+    containers >= 0.5.0,
+    mtl >= 2.1.0,
+    transformers >= 0.3.0
+
+executable hsclearimports
+  main-is: hsclearimports.hs
+  hs-source-dirs: tools
+  build-depends:
+    base >= 4.7 && < 5,
+    hsdev,
+    directory >= 1.2.0,
+    ghc >= 7.8.1,
+    haskell-src-exts >= 1.14.0,
+    containers >= 0.5.0,
+    mtl >= 2.1.0
+
+executable hscabal
+  main-is: hscabal.hs
+  hs-source-dirs: tools
+  other-modules:
+    System.Command
+    Tool
+  build-depends:
+    base >= 4.7 && < 5,
+    hsdev,
+    aeson >= 0.7.0,
+    aeson-pretty >= 0.7.0,
+    bytestring >= 0.10.0,
+    containers >= 0.5.0,
+    mtl >= 2.1.0
+
+executable hshayoo
+  main-is: hshayoo.hs
+  hs-source-dirs: tools
+  other-modules:
+    System.Command
+    Tool
+  build-depends:
+    base >= 4.7 && < 5,
+    hsdev,
+    aeson >= 0.7.0,
+    aeson-pretty >= 0.7.0,
+    bytestring >= 0.10.0,
+    containers >= 0.5.0,
+    mtl >= 2.1.0
+
+test-suite test
+  main-is: Test.hs
+  hs-source-dirs: tests
+  type: exitcode-stdio-1.0
+  build-depends:
+    base >= 4.7 && < 5
+
+source-repository head
+  type: git
+  location: git://github.com/mvoidex/hsdev
+ src/Data/Async.hs view
@@ -0,0 +1,44 @@+module Data.Async (
+	Event(..),
+	event,
+	Async(..),
+	newAsync, readAsync, modifyAsync
+	) where
+
+import Control.DeepSeq (NFData, force)
+import Control.Monad (forM_)
+import Control.Concurrent
+
+import Data.Group (Group(..))
+
+-- | Event on async value
+data Event a = Append a | Remove a | Clear | Modify (a -> a) | Action (a -> IO a)
+
+-- | Event to function
+event :: Group a => Event a -> a -> IO a
+event (Append v) x = return $ add x v
+event (Remove v) x = return $ sub x v
+event Clear _ = return zero
+event (Modify p) x = return $ p x
+event (Action p) x = p x
+
+data Async a = Async {
+	asyncVar :: MVar a,
+	asyncEvents :: Chan (Event a) }
+
+newAsync :: (NFData a, Group a) => IO (Async a)
+newAsync = do
+	var <- newMVar zero
+	events <- newChan
+	forkIO $ do
+		evs <- getChanContents events
+		forM_ evs $ \e -> modifyMVar_ var $ \val -> do
+			x' <- event e val
+			force x' `seq` return x'
+	return $ Async var events
+
+readAsync :: Async a -> IO a
+readAsync = readMVar . asyncVar
+
+modifyAsync :: Async a -> Event a -> IO ()
+modifyAsync avar = writeChan (asyncEvents avar)
+ src/Data/Group.hs view
@@ -0,0 +1,40 @@+{-# LANGUAGE TypeSynonymInstances #-}
+
+module Data.Group (
+	Group(..),
+	groupSum
+	) where
+
+import Data.List ((\\))
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Set (Set)
+import qualified Data.Set as S
+
+-- | Group is monoid with invertibility
+-- But for our purposes we prefer two functions: `add` and `sub`.
+class Eq a => Group a where
+	add :: a -> a -> a
+	sub :: a -> a -> a
+	zero :: a
+
+instance Eq a => Group [a] where
+	add = (++)
+	sub = (\\)
+	zero = []
+
+instance Ord a => Group (Set a) where
+	add = S.union
+	sub = S.difference
+	zero = S.empty
+
+instance (Ord k, Group a) => Group (Map k a) where
+	add = M.unionWith add
+	sub = M.differenceWith sub' where
+		sub' x y = if z == zero then Nothing else Just z where
+			z = sub x y
+	zero = M.empty
+
+-- | Sums list
+groupSum :: Group a => [a] -> a
+groupSum = foldr add zero
+ src/HsDev.hs view
@@ -0,0 +1,17 @@+module HsDev (
+	module HsDev.Symbols,
+	module HsDev.Database,
+	module HsDev.Tools.GhcMod,
+	module HsDev.Scan,
+	module HsDev.Project,
+	module HsDev.Cache,
+	module HsDev.Commands
+	) where
+
+import HsDev.Symbols
+import HsDev.Database
+import HsDev.Tools.GhcMod
+import HsDev.Scan
+import HsDev.Project
+import HsDev.Cache
+import HsDev.Commands
+ src/HsDev/Cabal.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Cabal (
+	Cabal(..), sandbox,
+	findPackageDb, locateSandbox,
+	cabalOpt
+	) where
+
+import Control.Applicative
+import Control.DeepSeq (NFData(..))
+import Control.Monad.Error
+import Data.Aeson
+import Data.List
+import System.Directory
+import System.FilePath ((</>))
+
+-- | Cabal or sandbox
+data Cabal = Cabal | Sandbox FilePath deriving (Eq, Ord)
+
+-- | Get sandbox
+sandbox :: Cabal -> Maybe FilePath
+sandbox Cabal = Nothing
+sandbox (Sandbox f) = Just f
+
+instance NFData Cabal where
+	rnf Cabal = ()
+	rnf (Sandbox p) = rnf p
+
+instance Show Cabal where
+	show Cabal = "<cabal>"
+	show (Sandbox p) = p
+
+instance ToJSON Cabal where
+	toJSON Cabal = toJSON ("cabal" :: String)
+	toJSON (Sandbox p) = toJSON $ object [
+		"sandbox" .= p]
+
+instance FromJSON Cabal where
+	parseJSON v = cabalP v <|> sandboxP v where
+		cabalP = withText "cabal" cabalText where
+			cabalText "cabal" = return Cabal
+			cabalText _ = fail "Unknown cabal string"
+		sandboxP = withObject "sandbox" sandboxPath where
+			sandboxPath obj = fmap Sandbox $ obj .: "sandbox"
+
+-- | Find -package-db path for sandbox
+findPackageDb :: FilePath -> IO (Maybe FilePath)
+findPackageDb sand = do
+	cts <- getDirectoryContents sand
+	return $ fmap (sand </>) $
+		find cabalDev cts <|> find cabalSandbox cts
+	where
+		cabalDev p = "packages-" `isPrefixOf` p && ".conf" `isSuffixOf` p
+		cabalSandbox p = "-packages.conf.d" `isSuffixOf` p
+
+-- | Create sandbox by parent directory
+locateSandbox :: FilePath -> ErrorT String IO Cabal
+locateSandbox p = liftIO (findPackageDb p) >>= maybe
+	(throwError $ "Can't locate package-db in sandbox: " ++ p)
+	(return . Sandbox)
+
+-- | Cabal ghc option
+cabalOpt :: Cabal -> [String]
+cabalOpt Cabal = []
+cabalOpt (Sandbox p) = ["-package-db " ++ p]
+ src/HsDev/Cache.hs view
@@ -0,0 +1,49 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Cache (
+	escapePath,
+	cabalCache,
+	projectCache,
+	standaloneCache,
+	dump,
+	load,
+	) where
+
+import Control.DeepSeq (force)
+import Data.Aeson (eitherDecode)
+import Data.Aeson.Encode.Pretty (encodePretty)
+import qualified Data.ByteString.Lazy.Char8 as BS
+import Data.Char (isAlphaNum)
+import Data.List (intercalate)
+import System.FilePath
+
+import HsDev.Symbols (Cabal(..))
+import HsDev.Project
+import HsDev.Database (Database)
+
+-- | Escape path
+escapePath :: FilePath -> FilePath
+escapePath = intercalate "." . map (filter isAlphaNum) . splitDirectories
+
+-- | Name of cache for cabal
+cabalCache :: Cabal -> FilePath
+cabalCache Cabal = "cabal" <.> "json"
+cabalCache (Sandbox p) = escapePath p <.> "json"
+
+-- | Name of cache for projects
+projectCache :: Project -> FilePath
+projectCache p = (escapePath . projectPath $ p) <.> "json"
+
+-- | Name of cache for standalone files
+standaloneCache :: FilePath
+standaloneCache = "standalone" <.> "json"
+
+-- | Dump database to file
+dump :: FilePath -> Database -> IO ()
+dump file = BS.writeFile file . encodePretty
+
+-- | Load database from file, strict
+load :: FilePath -> IO (Either String Database)
+load file = do
+	cts <- BS.readFile file
+	return $ force $ eitherDecode cts
+ src/HsDev/Cache/Structured.hs view
@@ -0,0 +1,76 @@+module HsDev.Cache.Structured (
+	dump, load,
+	loadCabal, loadProject, loadFiles
+	) where
+
+import Control.Applicative
+import Control.Exception
+import Control.Monad.Error
+import qualified Data.Map as M (assocs)
+import Data.Monoid
+import System.Directory
+import System.FilePath
+
+import Data.Group (Group(zero))
+import qualified HsDev.Cache as Cache
+import HsDev.Cabal (Cabal)
+import HsDev.Database
+import HsDev.Symbols
+import HsDev.Project (project)
+import HsDev.Util
+
+-- | Write cache
+dump :: FilePath -> Structured -> IO ()
+dump dir db = do
+	createDirectoryIfMissing True (dir </> "cabal")
+	createDirectoryIfMissing True (dir </> "projects")
+	forM_ (M.assocs $ structuredCabals db) $ \(c, cdb) -> Cache.dump
+		(dir </> "cabal" </> Cache.cabalCache c)
+		cdb
+	forM_ (M.assocs $ structuredProjects db) $ \(p, pdb) -> Cache.dump
+		(dir </> "projects" </> Cache.projectCache (project p))
+		pdb
+	files' <- either (const zero) id <$>
+		handle wrapIO
+		(Cache.load (dir </> Cache.standaloneCache))
+	Cache.dump (dir </> Cache.standaloneCache) $
+		files' `mappend` structuredFiles db
+	where
+		wrapIO :: SomeException -> IO (Either String Database)
+		wrapIO = return . Left . show
+
+-- | Load all cache
+load :: FilePath -> IO (Either String Structured)
+load dir = runErrorT $ join $ either throwError return <$> (structured <$> loadCabals <*> loadProjects <*> loadFiles) where
+	loadCabals = loadDir (dir </> "cabal")
+	loadProjects = loadDir (dir </> "projects")
+	loadFiles = ErrorT $ Cache.load (dir </> Cache.standaloneCache)
+
+	loadDir p = do
+		fs <- liftIO $ liftM (filter ((== ".json") . takeExtension)) $ directoryContents p
+		mapM (ErrorT . Cache.load) fs
+
+-- | Load data from cache
+loadData :: FilePath -> ErrorT String IO Database
+loadData = liftExceptionM . ErrorT . Cache.load
+
+-- | Load cabal from cache
+loadCabal :: Cabal -> FilePath -> ErrorT String IO Structured
+loadCabal c dir = do
+	dat <- loadData (dir </> "cabal" </> Cache.cabalCache c)
+	ErrorT $ return $ structured [dat] [] mempty
+
+-- | Load project from cache
+loadProject :: FilePath -> FilePath -> ErrorT String IO Structured
+loadProject p dir = do
+	dat <- loadData (dir </> "projects" </> Cache.projectCache (project p))
+	ErrorT $ return $ structured [] [dat] mempty
+
+-- | Load standalone files
+loadFiles :: [FilePath] -> FilePath -> ErrorT String IO Structured
+loadFiles fs dir = do
+	dat <- loadData (dir </> Cache.standaloneCache)
+	ErrorT $ return $ structured [] [] $ filterDB inFiles (const False) dat
+	where
+		inFiles = maybe False (`elem` fs') . moduleSource . moduleLocation
+		fs' = map normalise fs
+ src/HsDev/Commands.hs view
@@ -0,0 +1,175 @@+{-# LANGUAGE RankNTypes, FlexibleContexts #-}
+
+module HsDev.Commands (
+	-- * Commands
+	findDeclaration, findModule,
+	fileModule,
+	lookupSymbol,
+	whois,
+	scopeModules, scope,
+	completions,
+	moduleCompletions,
+
+	-- * Filters
+	checkModule, checkDeclaration, restrictCabal, visibleFrom,
+	splitIdentifier,
+
+	-- * Helpers
+	fileCtx, fileCtxMaybe
+	) 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.Traversable (traverse)
+import System.Directory (canonicalizePath)
+
+import HsDev.Database
+import HsDev.Project
+import HsDev.Symbols
+import HsDev.Symbols.Util
+
+-- | Find declaration by name
+findDeclaration :: Database -> String -> ErrorT String IO [ModuleDeclaration]
+findDeclaration db ident = return $ selectDeclarations ((== ident) . declarationName . moduleDeclaration) db
+
+-- | Find module by name
+findModule :: Database -> String -> ErrorT String IO [Module]
+findModule db mname = return $ selectModules ((== mname) . moduleName) db
+
+-- | Find module in file
+fileModule :: Database -> FilePath -> ErrorT String IO Module
+fileModule db src = do
+	src' <- liftIO $ canonicalizePath src
+	maybe (throwError $ "File '" ++ src' ++ "' not found") return $ lookupFile src' db
+
+-- | Find project of module
+getProject :: Database -> Project -> ErrorT String IO Project
+getProject db p = do
+	p' <- liftIO $ canonicalizePath $ projectCabal p
+	maybe (throwError $ "Project " ++ p' ++ " not found") return $
+		M.lookup p' $ databaseProjects db
+
+-- | Lookup visible symbol
+lookupSymbol :: Database -> Cabal -> FilePath -> String -> ErrorT String IO [ModuleDeclaration]
+lookupSymbol db cabal file ident = do
+	(_, mthis, mproj) <- fileCtx db file
+	liftM
+		(filter $ checkModule $ allOf [
+			restrictCabal cabal,
+			visibleFrom mproj mthis,
+			maybe (const True) inModule qname])
+		(findDeclaration db iname)
+	where
+		(qname, iname) = splitIdentifier ident
+
+-- | 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])
+		(findDeclaration db iname)
+	where
+		(qname, iname) = splitIdentifier ident
+
+-- | Accessible modules
+scopeModules :: Database -> Cabal -> FilePath -> ErrorT String IO [Module]
+scopeModules db cabal file = do
+	(file', mthis, mproj) <- fileCtxMaybe db file
+	case mproj of
+		Nothing -> return $ maybe id (:) mthis $ selectModules (inCabal cabal . moduleId) db
+		Just proj -> let deps' = deps file' proj in
+			return $ concatMap (\p -> selectModules (p . moduleId) db) [
+				inProject proj,
+				\m -> any (`inPackage` m) deps']
+	where
+		deps f p = maybe [] infoDepends $ fileTarget p f
+
+-- | 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
+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)]
+	where
+		(qname, iname) = splitIdentifier prefix
+
+-- | Module completions
+moduleCompletions :: Database -> [Module] -> String -> ErrorT String IO [String]
+moduleCompletions _ ms prefix = return $ 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)
+			| otherwise = Nothing
+
+-- | Check module
+checkModule :: (ModuleId -> Bool) -> (ModuleDeclaration -> Bool)
+checkModule = (. declarationModuleId)
+
+-- | Check declaration
+checkDeclaration :: (Declaration -> Bool) -> (ModuleDeclaration -> Bool)
+checkDeclaration = (. moduleDeclaration)
+
+-- | Allow only selected cabal sandbox
+restrictCabal :: Cabal -> ModuleId -> Bool
+restrictCabal cabal m = inCabal cabal m || not (byCabal m)
+
+-- | Check whether module is visible from source file
+visibleFrom :: Maybe Project -> Module -> ModuleId -> Bool
+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
+
+-- | Split identifier into module name and identifier itself
+splitIdentifier :: String -> (Maybe String, String)
+splitIdentifier name = (qname, name') where
+	prefix = dropWhileEnd (/= '.') name
+	prefix' = dropWhileEnd (== '.') prefix
+	qname = if null prefix' then Nothing else Just prefix'
+	name' = fromMaybe (error "Impossible happened") $ stripPrefix prefix name
+
+-- | Get context file and project
+fileCtx :: Database -> FilePath -> ErrorT String IO (FilePath, Module, Maybe Project)
+fileCtx db file = do
+	file' <- liftIO $ canonicalizePath file
+	mthis <- fileModule db file'
+	mproj <- traverse (getProject db) $ projectOf $ moduleId mthis
+	return (file', mthis, mproj)
+
+-- | Try get context file
+fileCtxMaybe :: Database -> FilePath -> ErrorT String IO (FilePath, Maybe Module, Maybe Project)
+fileCtxMaybe db file = ((\(f, m, p) -> (f, Just m, p)) <$> fileCtx db file) <|> onlyProj where
+	onlyProj = do
+		file' <- liftIO $ canonicalizePath file
+		mproj <- liftIO $ locateProject file'
+		mproj' <- traverse (getProject db) mproj
+		return (file', Nothing, mproj')
+ src/HsDev/Database.hs view
@@ -0,0 +1,237 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Database (
+	Database(..),
+	databaseIntersection, nullDatabase, databaseLocals, allModules, allDeclarations,
+	fromModule, fromProject,
+	filterDB,
+	projectDB, cabalDB, standaloneDB,
+	selectModules, selectDeclarations, lookupModule, lookupFile,
+	getInspected,
+
+	append, remove,
+
+	Structured(..),
+	structured, structurize, merge
+	) where
+
+import Control.Applicative
+import Control.Monad (msum, join)
+import Control.DeepSeq (NFData(..))
+import Data.Aeson
+import Data.Either (rights)
+import Data.Function (on)
+import Data.Group (Group(..))
+import Data.List (nub)
+import Data.Map (Map)
+import Data.Maybe
+import Data.Monoid (Monoid(..))
+import qualified Data.Map as M
+
+import HsDev.Symbols
+import HsDev.Symbols.Util
+import HsDev.Project
+import HsDev.Util ((.::))
+
+-- | HsDev database
+data Database = Database {
+	databaseModules :: Map ModuleLocation InspectedModule,
+	databaseProjects :: Map FilePath Project }
+		deriving (Eq, Ord)
+
+instance NFData Database where
+	rnf (Database ms ps) = rnf ms `seq` rnf ps
+
+instance Group Database where
+	add old new = Database {
+		databaseModules = databaseModules new `M.union` databaseModules old,
+		databaseProjects = M.unionWith mergeProject (databaseProjects new) (databaseProjects old) }
+		where
+			mergeProject pl pr = pl {
+				projectDescription = msum [projectDescription pl, projectDescription pr] }
+	sub old new = Database {
+		databaseModules = databaseModules old `M.difference` databaseModules new,
+		databaseProjects = databaseProjects old `M.difference` databaseProjects new }
+	zero = Database M.empty M.empty
+
+instance Monoid Database where
+	mempty = zero
+	mappend = add
+
+instance ToJSON Database where
+	toJSON (Database ms ps) = object [
+		"modules" .= M.elems ms,
+		"projects" .= M.elems ps]
+
+instance FromJSON Database where
+	parseJSON = withObject "database" $ \v -> Database <$>
+		((M.unions . map mkModule) <$> v .:: "modules") <*>
+		((M.unions . map mkProject) <$> v .:: "projects")
+		where
+			mkModule m = M.singleton (inspectedId m) m
+			mkProject p = M.singleton (projectCabal p) p
+
+-- | Database intersection, prefers first database data
+databaseIntersection :: Database -> Database -> Database
+databaseIntersection l r = mempty {
+	databaseModules = databaseModules l `M.intersection` databaseModules r,
+	databaseProjects = databaseProjects l `M.intersection` databaseProjects r }
+
+-- | Check if database is empty
+nullDatabase :: Database -> Bool
+nullDatabase db = M.null (databaseModules db) && M.null (databaseProjects db)
+
+-- | Bring all locals to scope
+databaseLocals :: Database -> Database
+databaseLocals db = db {
+	databaseModules = M.map (fmap moduleLocals) (databaseModules db) }
+
+-- | All modules
+allModules :: Database -> [Module]
+allModules = rights . map inspectionResult . M.elems . databaseModules
+
+-- | All declarations
+allDeclarations :: Database -> [ModuleDeclaration]
+allDeclarations db = do
+	m <- allModules db
+	moduleModuleDeclarations m
+
+-- | Make database from module
+fromModule :: InspectedModule -> Database
+fromModule m = zero {
+	databaseModules = M.singleton (inspectedId m) m }
+
+-- | Make database from project
+fromProject :: Project -> Database
+fromProject p = zero {
+	databaseProjects = M.singleton (projectCabal p) p }
+
+-- | Filter database by predicate
+filterDB :: (Module -> Bool) -> (Project -> Bool) -> Database -> Database
+filterDB m p db = mempty {
+	databaseModules = M.filter (either (const False) m . inspectionResult) (databaseModules db),
+	databaseProjects = M.filter p (databaseProjects db) }
+
+-- | Project database
+projectDB :: Project -> Database -> Database
+projectDB proj = filterDB (inProject proj . moduleId) (((==) `on` projectCabal) proj)
+
+-- | Cabal database
+cabalDB :: Cabal -> Database -> Database
+cabalDB cabal = filterDB (inCabal cabal . moduleId) (const False)
+
+-- | Standalone database
+standaloneDB :: Database -> Database
+standaloneDB db = filterDB (noProject . moduleId) (const False) db where
+	noProject m = all (not . flip inProject m) ps
+	ps = M.elems $ databaseProjects db
+
+-- | Select module by predicate
+selectModules :: (Module -> Bool) -> Database -> [Module]
+selectModules p = filter p . allModules
+
+-- | Select declaration by predicate
+selectDeclarations :: (ModuleDeclaration -> Bool) -> Database -> [ModuleDeclaration]
+selectDeclarations p = filter p . allDeclarations
+
+-- | Lookup module by its location and name
+lookupModule :: ModuleLocation -> Database -> Maybe Module
+lookupModule mloc db = do
+	m <- M.lookup mloc $ databaseModules db
+	either (const Nothing) Just $ inspectionResult m
+
+-- | Lookup module by its source file
+lookupFile :: FilePath -> Database -> Maybe Module
+lookupFile f = listToMaybe . selectModules (inFile f . moduleId)
+
+-- | Get inspected module
+getInspected :: Database -> Module -> InspectedModule
+getInspected db m = fromMaybe err $ M.lookup (moduleLocation m) $ databaseModules db where
+	err = error "Impossible happened: getInspected"
+
+-- | Append database
+append :: Database -> Database -> Database
+append = add
+
+-- | Remove database
+remove :: Database -> Database -> Database
+remove = sub
+
+-- | Structured database
+data Structured = Structured {
+	structuredCabals :: Map Cabal Database,
+	structuredProjects :: Map FilePath Database,
+	structuredFiles :: Database }
+		deriving (Eq, Ord)
+
+instance NFData Structured where
+	rnf (Structured cs ps fs) = rnf cs `seq` rnf ps `seq` rnf fs
+
+instance Group Structured where
+	add old new = Structured {
+		structuredCabals = structuredCabals new `M.union` structuredCabals old,
+		structuredProjects = structuredProjects new `M.union` structuredProjects old,
+		structuredFiles = structuredFiles old `add` structuredFiles new }
+	sub old new = Structured {
+		structuredCabals = structuredCabals old `M.difference` structuredCabals new,
+		structuredProjects = structuredProjects old `M.difference` structuredProjects new,
+		structuredFiles = structuredFiles old `sub` structuredFiles new }
+	zero = Structured zero zero zero
+
+instance Monoid Structured where
+	mempty = zero
+	mappend = add
+
+instance ToJSON Structured where
+	toJSON (Structured cs ps fs) = object [
+		"cabals" .= M.elems cs,
+		"projects" .= M.elems ps,
+		"files" .= fs]
+
+instance FromJSON Structured where
+	parseJSON = withObject "structured" $ \v -> join $
+		either fail return <$> (structured <$>
+			(v .:: "cabals") <*>
+			(v .:: "projects") <*>
+			(v .:: "files"))
+
+structured :: [Database] -> [Database] -> Database -> Either String Structured
+structured cs ps fs = Structured <$> mkMap keyCabal cs <*> mkMap keyProj ps <*> pure fs where
+	mkMap :: Ord a => (Database -> Either String a) -> [Database] -> Either String (Map a Database)
+	mkMap key dbs = do
+		keys <- mapM key dbs
+		return $ M.fromList $ zip keys dbs
+	keyCabal :: Database -> Either String Cabal
+	keyCabal db = unique
+		"No cabal"
+		"Different module cabals"
+		(nub <$> mapM getCabal (allModules db))
+		where
+			getCabal m = case moduleLocation m of
+				CabalModule c _ _ -> Right c
+				_ -> Left "Module have no cabal"
+	keyProj :: Database -> Either String FilePath
+	keyProj db = unique
+		"No project"
+		"Different module projects"
+		(return (M.keys (databaseProjects db)))
+	-- Check that list results in one element
+	unique :: (Eq a) => String -> String -> Either String [a] -> Either String a
+	unique _ _ (Left e) = Left e
+	unique no _ (Right []) = Left no
+	unique _ _ (Right [x]) = Right x
+	unique _ much (Right _) = Left much
+
+structurize :: Database -> Structured
+structurize db = Structured cs ps fs where
+	cs = M.fromList [(c, cabalDB c db) | c <- nub (mapMaybe modCabal (allModules db))]
+	ps = M.fromList [(pname, projectDB (project pname) db) | pname <- M.keys (databaseProjects db)]
+	fs = standaloneDB db
+
+merge :: Structured -> Database
+merge (Structured cs ps fs) = mconcat $ M.elems cs ++ M.elems ps ++ [fs]
+
+modCabal :: Module -> Maybe Cabal
+modCabal m = case moduleLocation m of
+	CabalModule c _ _ -> Just c
+	_ -> Nothing
+ src/HsDev/Database/Async.hs view
@@ -0,0 +1,24 @@+module HsDev.Database.Async (
+	update, wait,
+	module Data.Async
+	) where
+
+import Control.Concurrent.MVar
+import Control.Monad.IO.Class (MonadIO(..))
+import Control.DeepSeq (force)
+
+import Data.Async
+
+import HsDev.Database (Database)
+
+update :: MonadIO m => Async Database -> m Database -> m ()
+update db act = do
+	db' <- act
+	force db' `seq` liftIO (modifyAsync db (Append db'))
+
+-- | This function is used to ensure that all previous updates were applied
+wait :: MonadIO m => Async Database -> m ()
+wait db = liftIO $ do
+	waitVar <- newEmptyMVar
+	modifyAsync db (Action $ \d -> putMVar waitVar () >> return d)
+	takeMVar waitVar
+ src/HsDev/Inspect.hs view
@@ -0,0 +1,252 @@+{-# LANGUAGE TypeSynonymInstances #-}
+
+module HsDev.Inspect (
+	analyzeModule,
+	inspectFile, fileInspection,
+	projectDirs, projectSources,
+	inspectProject
+	) where
+
+import Control.Arrow
+import Control.Applicative
+import Control.DeepSeq
+import qualified Control.Exception as E
+import Control.Monad
+import Control.Monad.Error
+import Data.List (intercalate, find)
+import Data.Map (Map)
+import Data.Maybe (fromMaybe, mapMaybe, catMaybes)
+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds)
+import Data.Traversable (traverse, sequenceA)
+import qualified Data.Map as M
+import qualified Language.Haskell.Exts as H
+import qualified Language.Haskell.Exts.Extension as H
+import qualified Documentation.Haddock as Doc
+import qualified System.Directory as Dir
+import System.IO
+import System.FilePath
+
+import qualified Name (Name, getOccString, occNameString)
+import qualified Module (moduleNameString)
+import qualified SrcLoc as Loc
+import qualified HsDecls
+import qualified HsBinds
+
+import HsDev.Symbols
+import HsDev.Project
+import HsDev.Tools.Base
+import HsDev.Tools.HDocs (hdocsProcess)
+import HsDev.Util
+
+-- | Analize source contents
+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,
+			moduleDocs =  Nothing,
+			moduleLocation = OtherModuleSource Nothing,
+			moduleExports = [],
+			moduleImports = map getImport imports,
+			moduleDeclarations = M.fromList $ map (declarationName &&& id) $ 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 }
+
+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
+
+getDecls :: [H.Decl] -> [Declaration]
+getDecls decls = map addLocals infos ++ filter noInfo defs where
+	infos = 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 infos
+
+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 $ H.prettyPrint typeSignature) [])))
+		names
+	H.TypeDecl loc n args _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (Type $ TypeInfo Nothing (map H.prettyPrint args) Nothing)]
+	H.DataDecl loc dataOrNew ctx n args _ _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (ctor dataOrNew $ TypeInfo (makeCtx ctx) (map H.prettyPrint args) Nothing)]
+	H.GDataDecl loc dataOrNew ctx n args _ _ _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (ctor dataOrNew $ TypeInfo (makeCtx ctx) (map H.prettyPrint args) Nothing)]
+	H.ClassDecl loc ctx n args _ _ -> [setPosition loc $ Declaration (identOfName n) Nothing Nothing (Class $ TypeInfo (makeCtx ctx) (map H.prettyPrint args) Nothing)]
+	_ -> []
+	where
+		ctor :: H.DataOrNew -> TypeInfo -> DeclarationInfo
+		ctor H.DataType = Data
+		ctor H.NewType = NewType
+
+		makeCtx [] = Nothing
+		makeCtx ctx = Just $ intercalate ", " $ map H.prettyPrint ctx
+
+getDef :: H.Decl -> [Declaration]
+getDef (H.FunBind []) = []
+getDef (H.FunBind matches@(H.Match loc n _ _ _ _ : _)) = [setPosition loc $ Declaration (identOfName n) Nothing Nothing 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
+	names :: H.Pat -> [H.Name]
+	names (H.PVar n) = [n]
+	names (H.PNeg n) = names n
+	names (H.PNPlusK n _) = [n]
+	names (H.PInfixApp l _ r) = names l ++ names r
+	names (H.PApp _ ns) = concatMap names ns
+	names (H.PTuple _ ns) = concatMap names ns
+	names (H.PList ns) = concatMap names ns
+	names (H.PParen n) = names n
+	names (H.PRec _ pf) = concatMap fieldNames pf
+	names (H.PAsPat n ns) = n : names ns
+	names H.PWildCard = []
+	names (H.PIrrPat n) = names n
+	names (H.PatTypeSig _ n _) = names n
+	names (H.PViewPat _ n) = names n
+	names (H.PBangPat n) = names n
+	names _ = []
+
+	fieldNames :: H.PatField -> [H.Name]
+	fieldNames (H.PFieldPat _ n) = names n
+	fieldNames (H.PFieldPun n) = [n]
+	fieldNames H.PFieldWildcard = []
+getDef _ = []
+
+identOfName :: H.Name -> String
+identOfName name = case name of
+	H.Ident s -> s
+	H.Symbol s -> s
+
+toPosition :: H.SrcLoc -> Position
+toPosition (H.SrcLoc _ l c) = Position l c
+
+setPosition :: H.SrcLoc -> Declaration -> Declaration
+setPosition loc d = d { declarationPosition = Just (toPosition loc) }
+
+-- | Get Map from declaration name to its documentation
+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 _ = []
+
+	extractNames :: HsDecls.LHsDecl Name.Name -> [String]
+	extractNames (Loc.L _ d) = case d of
+		HsDecls.TyClD ty -> [locatedName $ HsDecls.tcdLName ty]
+		HsDecls.SigD sig -> case sig of
+			HsBinds.TypeSig names _ -> map locatedName names
+			HsBinds.GenericSig names _ -> map locatedName names
+			_ -> []
+		_ -> []
+
+	extractDocs :: Doc.DocForDecl Name.Name -> Maybe String
+	extractDocs (mbDoc, _) = fmap printDoc $ Doc.documentationDoc mbDoc where
+		printDoc :: Doc.Doc Name.Name -> String
+		printDoc Doc.DocEmpty = ""
+		printDoc (Doc.DocAppend l r) = printDoc l ++ printDoc r
+		printDoc (Doc.DocString s) = s
+		printDoc (Doc.DocParagraph p) = printDoc p
+		printDoc (Doc.DocIdentifier i) = Name.getOccString i
+		printDoc (Doc.DocIdentifierUnchecked (m, i)) = Module.moduleNameString m ++ "." ++ Name.occNameString i
+		printDoc (Doc.DocModule m) = m
+		printDoc (Doc.DocWarning w) = printDoc w
+		printDoc (Doc.DocEmphasis e) = printDoc e
+		printDoc (Doc.DocMonospaced m) = printDoc m
+		printDoc (Doc.DocUnorderedList lst) = concatMap printDoc lst -- Is this right?
+		printDoc (Doc.DocOrderedList lst) = concatMap printDoc lst -- And this
+		printDoc (Doc.DocDefList defs) = concatMap (\(l, r) -> printDoc l ++ " = " ++ printDoc r) defs -- ?
+		printDoc (Doc.DocCodeBlock code) = printDoc code
+		printDoc (Doc.DocPic pic) = show pic
+		printDoc (Doc.DocAName a) = a
+		printDoc (Doc.DocExamples exs) = unlines $ map showExample exs where
+			showExample (Doc.Example expr results) = expr ++ " => " ++ intercalate ", " results
+		printDoc (Doc.DocHyperlink link) = fromMaybe (Doc.hyperlinkUrl link) (Doc.hyperlinkLabel link)
+		printDoc (Doc.DocProperty prop) = prop
+		-- Catch all unsupported ones
+		-- printDoc _ = "[unsupported-by-extractDocs]" -- TODO
+
+	locatedName :: Loc.Located Name.Name -> String
+	locatedName (Loc.L _ nm) = Name.getOccString nm
+
+-- | Adds documentation to declaration
+addDoc :: Map String String -> Declaration -> Declaration
+addDoc docsMap decl = decl { declarationDocs = M.lookup (declarationName decl) 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) }
+
+-- | Inspect file
+inspectFile :: [String] -> FilePath -> ErrorT String IO InspectedModule
+inspectFile opts file = do
+	let
+		noReturn :: E.SomeException -> IO [Doc.Interface]
+		noReturn _ = return []
+	proj <- liftIO $ locateProject file
+	absFilename <- liftIO $ Dir.canonicalizePath file
+	inspect (FileModule absFilename proj) (fileInspection absFilename opts) $ do
+		docsMap <- liftIO $ hdocsProcess absFilename opts
+		--docsMap <- liftIO $ 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
+		--		return (mfile, i)
+		forced <- ErrorT $ E.handle onError $ do
+			analyzed <- liftM (analyzeModule exts (Just absFilename)) $ readFileUtf8 absFilename
+			force analyzed `deepseq` return analyzed
+			--E.evaluate $ force analyzed
+		return $ setLoc absFilename proj . maybe id addDocs docsMap $ forced
+	where
+		setLoc f p m = m { moduleLocation = FileModule f p }
+		onError :: E.ErrorCall -> IO (Either String Module)
+		onError = return . Left . show
+
+		exts = mapMaybe flagExtension opts
+
+-- | File inspection data
+fileInspection :: FilePath -> [String] -> ErrorT String IO Inspection
+fileInspection f opts = do
+	tm <- liftIO $ Dir.getModificationTime f
+	return $ InspectionAt (utcTimeToPOSIXSeconds tm) 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'
+
+-- | 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
+
+-- | Inspect project
+inspectProject :: [String] -> Project -> ErrorT String IO (Project, [InspectedModule])
+inspectProject opts p = do
+	p' <- loadProject p
+	srcs <- projectSources p'
+	modules <- mapM inspectFile' srcs
+	return (p', catMaybes modules)
+	where
+		inspectFile' exts = liftM return (inspectFile (opts ++ extensionsOpts (extensions exts)) (entity exts)) <|> return Nothing
+
+-- | Read file in UTF8
+readFileUtf8 :: FilePath -> IO String
+readFileUtf8 f = withFile f ReadMode $ \h -> do
+	hSetEncoding h utf8
+	cts <- hGetContents h
+	length cts `seq` return cts
+ src/HsDev/Project.hs view
@@ -0,0 +1,344 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Project (
+	Project(..),
+	ProjectDescription(..), Target(..), Library(..), Executable(..), Test(..), Info(..),
+	readProject, loadProject,
+	project,
+	Extensions(..), withExtensions,
+	infos, inTarget, fileTarget, findSourceDir, sourceDirs,
+
+	-- * Helpers
+	showExtension, flagExtension, extensionFlag,
+	extensionsOpts
+	) where
+
+import Control.Applicative
+import Control.Arrow
+import Control.DeepSeq (NFData(..))
+import Control.Exception
+import Control.Monad.Error
+import Data.Aeson
+import Data.Aeson.Types (Parser)
+import Data.List
+import Data.Maybe
+import Data.Monoid
+import Data.Foldable (Foldable(..))
+import Data.Traversable
+import qualified Distribution.Package as P
+import qualified Distribution.PackageDescription as PD
+import Distribution.PackageDescription.Parse
+import Distribution.ModuleName (components)
+import Distribution.Text
+import qualified Distribution.Text (Text)
+import Language.Haskell.Extension
+import System.FilePath
+
+import HsDev.Util
+
+-- | Cabal project
+data Project = Project {
+	projectName :: String,
+	projectPath :: FilePath,
+	projectCabal :: FilePath,
+	projectDescription :: Maybe ProjectDescription }
+		deriving (Read)
+
+instance NFData Project where
+	rnf (Project n p c _) = rnf n `seq` rnf p `seq` rnf c
+
+instance Eq Project where
+	l == r = projectCabal l == projectCabal r
+
+instance Ord Project where
+	compare l r = compare (projectName l, projectCabal l) (projectName r, projectCabal r)
+
+instance Show Project where
+	show p = unlines $ [
+		"project " ++ projectName p,
+		"\tcabal: " ++ projectCabal p,
+		"\tdescription:"] ++ concatMap (map (tab 2) . lines . show) (maybeToList $ projectDescription p)
+
+instance ToJSON Project where
+	toJSON p = object [
+		"name" .= projectName p,
+		"path" .= projectPath p,
+		"cabal" .= projectCabal p,
+		"description" .= projectDescription p]
+
+instance FromJSON Project where
+	parseJSON = withObject "project" $ \v -> Project <$>
+		v .:: "name" <*>
+		v .:: "path" <*>
+		v .:: "cabal" <*>
+		v .:: "description"
+
+data ProjectDescription = ProjectDescription {
+	projectLibrary :: Maybe Library,
+	projectExecutables :: [Executable],
+	projectTests :: [Test] }
+		deriving (Eq, Read)
+
+instance Show ProjectDescription where
+	show pd = unlines $
+		concatMap (lines . show) (maybeToList (projectLibrary pd)) ++
+		concatMap (lines . show) (projectExecutables pd) ++
+		concatMap (lines . show) (projectTests pd)
+
+instance ToJSON ProjectDescription where
+	toJSON d = object [
+		"library" .= projectLibrary d,
+		"executables" .= projectExecutables d,
+		"tests" .= projectTests d]
+
+instance FromJSON ProjectDescription where
+	parseJSON = withObject "project description" $ \v -> ProjectDescription <$>
+		v .:: "library" <*>
+		v .:: "executables" <*>
+		v .:: "tests"
+
+class Target a where
+	buildInfo :: a -> Info
+
+-- | Library in project
+data Library = Library {
+	libraryModules :: [[String]],
+	libraryBuildInfo :: Info }
+		deriving (Eq, Read)
+
+instance Target Library where
+	buildInfo = libraryBuildInfo
+
+instance Show Library where
+	show l = unlines $
+		["library", "\tmodules:"] ++
+		(map (tab 2 . intercalate ".") $ libraryModules l) ++
+		(map (tab 1) . lines . show $ libraryBuildInfo l)
+
+instance ToJSON Library where
+	toJSON l = object [
+		"modules" .= fmap (intercalate ".") (libraryModules l),
+		"info" .= libraryBuildInfo l]
+
+instance FromJSON Library where
+	parseJSON = withObject "library" $ \v -> Library <$> (fmap splitModule <$> v .:: "modules") <*> v .:: "info" where
+		splitModule :: String -> [String]
+		splitModule = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break (== '.'))
+
+-- | Executable
+data Executable = Executable {
+	executableName :: String,
+	executablePath :: FilePath,
+	executableBuildInfo :: Info }
+		deriving (Eq, Read)
+
+instance Target Executable where
+	buildInfo = executableBuildInfo
+
+instance Show Executable where
+	show e = unlines $
+		["executable " ++ executableName e, "\tpath: " ++ executablePath e] ++
+		(map (tab 1) . lines . show $ executableBuildInfo e)
+
+instance ToJSON Executable where
+	toJSON e = object [
+		"name" .= executableName e,
+		"path" .= executablePath e,
+		"info" .= executableBuildInfo e]
+
+instance FromJSON Executable where
+	parseJSON = withObject "executable" $ \v -> Executable <$>
+		v .:: "name" <*>
+		v .:: "path" <*>
+		v .:: "info"
+
+-- | Test
+data Test = Test {
+	testName :: String,
+	testEnabled :: Bool,
+	testBuildInfo :: Info }
+		deriving (Eq, Read)
+
+instance Target Test where
+	buildInfo = testBuildInfo
+
+instance Show Test where
+	show t = unlines $
+		["test " ++ testName t, "\tenabled: " ++ show (testEnabled t)] ++
+		(map (tab 1) . lines . show $ testBuildInfo t)
+
+instance ToJSON Test where
+	toJSON t = object [
+		"name" .= testName t,
+		"enabled" .= testEnabled t,
+		"info" .= testBuildInfo t]
+
+instance FromJSON Test where
+	parseJSON = withObject "test" $ \v -> Test <$>
+		v .:: "name" <*>
+		v .:: "enabled" <*>
+		v .:: "info"
+
+-- | Build info
+data Info = Info {
+	infoDepends :: [String],
+	infoLanguage :: Maybe Language,
+	infoExtensions :: [Extension],
+	infoSourceDirs :: [FilePath] }
+		deriving (Eq, Read)
+
+instance Show Info where
+	show i = unlines $ lang ++ exts ++ sources where
+		lang = maybe [] (\l -> ["default-language: " ++ display l]) $ infoLanguage i
+		exts
+			| null (infoExtensions i) = []
+			| otherwise = ["extensions:"] ++ map (tab 1) (map display (infoExtensions i))
+		sources = ["source-dirs:"] ++
+			(map (tab 1) $ infoSourceDirs i)
+
+instance ToJSON Info where
+	toJSON i = object [
+		"build-depends" .= infoDepends i,
+		"language" .= fmap display (infoLanguage i),
+		"extensions" .= map display (infoExtensions i),
+		"source-dirs" .= infoSourceDirs i]
+
+instance FromJSON Info where
+	parseJSON = withObject "info" $ \v -> Info <$>
+		v .: "build-depends" <*>
+		((v .:: "language") >>= traverse (parseDT "Language")) <*>
+		((v .:: "extensions") >>= traverse (parseDT "Extension")) <*>
+		v .:: "source-dirs"
+
+-- | Analyze cabal file
+analyzeCabal :: String -> Either String ProjectDescription
+analyzeCabal source = case liftM flattenDescr $ parsePackageDescription source of
+	ParseOk _ r -> Right ProjectDescription {
+		projectLibrary = fmap toLibrary $ PD.library r,
+		projectExecutables = fmap toExecutable $ PD.executables r,
+		projectTests = fmap toTest $ PD.testSuites r }
+	ParseFailed e -> Left $ "Parse failed: " ++ show e
+	where
+		toLibrary (PD.Library exposeds _ info) = Library (map components exposeds) (toInfo info)
+		toExecutable (PD.Executable name path info) = Executable name path (toInfo info)
+		toTest (PD.TestSuite name _ info enabled) = Test name enabled (toInfo info)
+		toInfo info = Info {
+			infoDepends = map pkgName (PD.targetBuildDepends info),
+			infoLanguage = PD.defaultLanguage info,
+			infoExtensions = PD.defaultExtensions info,
+			infoSourceDirs = PD.hsSourceDirs info }
+
+		pkgName :: P.Dependency -> String
+		pkgName (P.Dependency (P.PackageName s) _) = s
+
+		flattenDescr :: PD.GenericPackageDescription -> PD.PackageDescription
+		flattenDescr (PD.GenericPackageDescription pkg _ mlib mexes mtests _) = pkg {
+			PD.library = flip fmap mlib $ flattenTree
+				(insert PD.libBuildInfo (\i l -> l { PD.libBuildInfo = i })),
+			PD.executables = flip fmap mexes $
+				second (flattenTree (insert PD.buildInfo (\i l -> l { PD.buildInfo = i }))) >>>
+				(\(n, e) -> e { PD.exeName = n }),
+			PD.testSuites = flip fmap mtests $
+				second (flattenTree (insert PD.testBuildInfo (\i l -> l { PD.testBuildInfo = i }))) >>>
+				(\(n, t) -> t { PD.testName = n }) }
+			where
+				insert :: (a -> PD.BuildInfo) -> (PD.BuildInfo -> a -> a) -> [P.Dependency] -> a -> a
+				insert f s deps x = s ((f x) { PD.targetBuildDepends = deps }) x
+
+		flattenTree :: Monoid a => (c -> a -> a) -> PD.CondTree v c a -> a
+		flattenTree f (PD.CondNode x cs cmps) = f cs x `mappend` mconcat (concatMap flattenBranch cmps) where
+			flattenBranch (_, t, mb) = flattenTree f t : map (flattenTree f) (maybeToList mb)
+
+-- | Read project info from .cabal
+readProject :: FilePath -> ErrorT String IO Project
+readProject file = do
+	source <- ErrorT $ handle (\e -> return (Left ("IO error: " ++ show (e :: IOException)))) (fmap Right $ readFile file)
+	length source `seq` either throwError (return . mkProject) $ analyzeCabal source
+	where
+		mkProject desc = (project file) {
+			projectDescription = Just desc }
+
+-- | Load project description
+loadProject :: Project -> ErrorT String IO Project
+loadProject p
+	| isJust (projectDescription p) = return p
+	| otherwise = readProject (projectCabal p)
+
+-- | Make project by .cabal file
+project :: FilePath -> Project
+project file = Project {
+	projectName = takeBaseName (takeDirectory file),
+	projectPath = takeDirectory file,
+	projectCabal = file,
+	projectDescription = Nothing }
+
+-- | Entity with project extensions
+data Extensions a = Extensions {
+	extensions :: [Extension],
+	entity :: a }
+
+instance Functor Extensions where
+	fmap f (Extensions e x) = Extensions e (f x)
+
+instance Applicative Extensions where
+	pure = Extensions []
+	(Extensions l f) <*> (Extensions r x) = Extensions (l ++ r) (f x)
+
+instance Foldable Extensions where
+	foldMap f (Extensions _ x) = f x
+
+instance Traversable Extensions where
+	traverse f (Extensions e x) = Extensions e <$> f x
+
+-- | Extensions for target
+withExtensions :: a -> Info -> Extensions a
+withExtensions x i = Extensions {
+	extensions = infoExtensions i,
+	entity = x }
+
+-- | Returns build targets infos
+infos :: ProjectDescription -> [Info]
+infos p =
+	maybe [] (return . libraryBuildInfo) (projectLibrary p) ++
+	map executableBuildInfo (projectExecutables p) ++
+	map testBuildInfo (projectTests p)
+
+-- | Check if source related to target, source must be relative to project directory
+inTarget :: FilePath -> Info -> Bool
+inTarget src info = any ((`isPrefixOf` normalise src) . normalise) $ infoSourceDirs info
+
+-- | Get target for source file
+fileTarget :: Project -> FilePath -> Maybe Info
+fileTarget p f = find (makeRelative (projectPath p) f `inTarget`) $
+	maybe [] infos $ projectDescription p
+
+-- | Finds source dir file belongs to
+findSourceDir :: Project -> FilePath -> Maybe FilePath
+findSourceDir p f = do
+	info <- fileTarget p f
+	listToMaybe $ filter (`isParent` f) $ map (projectPath p </>) $ infoSourceDirs info
+
+-- | Returns source dirs for library, executables and tests
+sourceDirs :: ProjectDescription -> [Extensions FilePath]
+sourceDirs = concatMap dirs . infos where
+	dirs i = map (`withExtensions` i) $ infoSourceDirs i
+
+parseDT :: Distribution.Text.Text a => String -> String -> Parser a
+parseDT typeName v = maybe err return (simpleParse v) where
+	err = fail $ "Can't parse " ++ typeName ++ ": " ++ v
+
+-- | Extension as flag name
+showExtension :: Extension -> String
+showExtension = display
+
+-- | Convert -Xext to ext
+flagExtension :: String -> Maybe String
+flagExtension = stripPrefix "-X"
+
+-- | Convert ext to -Xext
+extensionFlag :: String -> String
+extensionFlag = ("-X" ++)
+
+-- | Extensions as opts to GHC
+extensionsOpts :: [Extension] -> [String]
+extensionsOpts = map (extensionFlag . showExtension)
+ src/HsDev/Scan.hs view
@@ -0,0 +1,92 @@+module HsDev.Scan (
+	-- * Enumerate functions
+	enumCabal, CompileFlag, ModuleToScan, ProjectToScan,
+	enumProject, enumDirectory,
+
+	-- * Scan
+	scanProjectFile,
+	scanModule, upToDate, rescanModule, changedModule, changedModules
+	) where
+
+import Control.Monad.Error
+import Data.List
+import qualified Data.Map as M
+import Data.Maybe (mapMaybe)
+import Data.Traversable (traverse)
+
+import HsDev.Symbols
+import HsDev.Database
+import HsDev.Tools.GhcMod
+import HsDev.Tools.GhcMod.InferType (inferTypes)
+import HsDev.Tools.HDocs
+import HsDev.Inspect
+import HsDev.Project
+import HsDev.Util
+
+-- | Enum cabal modules
+enumCabal :: [String] -> Cabal -> ErrorT String IO [ModuleLocation]
+enumCabal = list
+
+-- | Compile flags
+type CompileFlag = String
+-- | Module with flags ready to scan
+type ModuleToScan = (ModuleLocation, [CompileFlag])
+-- | Project ready to scan
+type ProjectToScan = (Project, [ModuleToScan])
+
+-- | Enum project sources
+enumProject :: Project -> ErrorT String IO ProjectToScan
+enumProject p = do
+	p' <- loadProject p
+	srcs <- projectSources p'
+	return (p', [(FileModule (entity src) (Just p'), extensionsOpts (extensions src)) | src <- srcs])
+
+-- | Enum directory modules
+enumDirectory :: FilePath -> ErrorT String IO ([ProjectToScan], [ModuleToScan])
+enumDirectory dir = do
+	files <- liftException $ traverseDirectory dir
+	let
+		projects = filter cabalFile files
+		sources = filter haskellSource files
+	projs <- mapM (enumProject . project) projects
+	let
+		projPaths = map (projectPath . fst) projs
+		projSources = concatMap (mapMaybe (moduleSource . fst) . snd) projs
+		standalone = map (\f -> FileModule f Nothing) $ filter (\s -> not (any (`isParent` s) projPaths)) sources
+	return (projs,  [(s, []) | s <- standalone])
+
+-- | Scan project file
+scanProjectFile :: [String] -> FilePath -> ErrorT String IO Project
+scanProjectFile opts f = do
+	proj <- (liftIO $ locateProject f) >>= maybe (throwError "Can't locate project") return
+	loadProject proj
+
+-- | Scan module
+scanModule :: [String] -> ModuleLocation -> ErrorT String IO InspectedModule
+scanModule opts (FileModule f p) = inspectFile opts f >>= traverse (inferTypes opts Cabal)
+scanModule opts (CabalModule c p n) = browse opts c n p
+scanModule opts (OtherModuleSource _) = throwError "Can inspect only modules in file or cabal"
+
+-- | Is inspected module up to date?
+upToDate :: [String] -> InspectedModule -> ErrorT String IO Bool
+upToDate opts (Inspected insp m _) = case m of
+	FileModule f p -> liftM (== insp) $ fileInspection f opts
+	CabalModule c p n -> return $ insp == browseInspection opts
+	_ -> return False
+
+-- | Rescan inspected module
+rescanModule :: [String] -> InspectedModule -> ErrorT String IO (Maybe InspectedModule)
+rescanModule opts im = do
+	up <- upToDate opts im
+	if up
+		then return Nothing
+		else fmap Just $ scanModule opts (inspectedId im)
+
+-- | Is module new or recently changed
+changedModule :: Database -> [String] -> ModuleLocation -> ErrorT String IO Bool
+changedModule db opts m = maybe (return True) (liftM not . upToDate opts) m' where
+	m' = M.lookup m (databaseModules db)
+
+-- | Returns new (to scan) and changed (to rescan) modules
+changedModules :: Database -> [String] -> [ModuleLocation] -> ErrorT String IO [ModuleLocation]
+changedModules db opts ms = filterM (changedModule db opts) ms
+ src/HsDev/Scan/Browse.hs view
@@ -0,0 +1,130 @@+module HsDev.Scan.Browse (
+	-- * Scan cabal modules
+	browseFilter, browse
+	) where
+
+import Control.Arrow
+import Control.Monad.Error
+import Data.Maybe
+import Data.Either
+import qualified Data.Map as M
+import Text.Read (readMaybe)
+
+import HsDev.Cabal
+import HsDev.Symbols
+import HsDev.Tools.Base (inspect)
+
+import qualified ConLike as GHC
+import qualified DataCon as GHC
+import qualified DynFlags as GHC
+import qualified Exception as GHC
+import qualified FastString as GHC
+import qualified GHC
+import qualified GhcMonad as GHC (liftIO)
+import qualified GHC.Paths as GHC
+import qualified Module as GHC
+import qualified Name as GHC
+import qualified Name as GHC
+import qualified Outputable as GHC
+import qualified Packages as GHC
+import qualified PatSyn as GHC
+import qualified TyCon as GHC
+import qualified Type as GHC
+import qualified Var as GHC
+import Pretty
+
+-- | Browse modules
+browseFilter :: [String] -> Cabal -> (ModuleLocation -> ErrorT String IO Bool) -> ErrorT String IO [InspectedModule]
+browseFilter opts cabal f = withPackages_ (cabalOpt cabal ++ opts) $ do
+	ms <- lift $ GHC.packageDbModules False
+	ms' <- filterM (mapErrorT GHC.liftIO . f . loc) ms
+	liftM catMaybes $ mapM browseModule' ms'
+	where
+		loc :: GHC.Module -> ModuleLocation
+		loc m = CabalModule cabal (readMaybe $ GHC.packageIdString $ GHC.modulePackageId m) (GHC.moduleNameString $ GHC.moduleName m)
+
+		browseModule' :: GHC.Module -> ErrorT String GHC.Ghc (Maybe InspectedModule)
+		browseModule' m = tryT $ inspect (loc m) (return $ InspectionAt 0 opts) (browseModule cabal m)
+
+-- | Browse all modules
+browse :: [String] -> Cabal -> ErrorT String IO [InspectedModule]
+browse opts cabal = browseFilter opts cabal (const $ return True)
+
+browseModule :: Cabal -> GHC.Module -> ErrorT String GHC.Ghc Module
+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),
+		moduleDocs = Nothing,
+		moduleLocation = mloc,
+		moduleExports = [],
+		moduleImports = [],
+		moduleDeclarations = M.fromList (map (declarationName &&& id) ds) }
+	where
+		mloc = CabalModule cabal (readMaybe $ GHC.packageIdString $ GHC.modulePackageId m) (GHC.moduleNameString $ GHC.moduleName m)
+		toDecl minfo n = do
+			tyInfo <- lift $ GHC.modInfoLookupName minfo n
+			tyResult <- lift $ maybe (inOtherModuleSource n) (return . Just) tyInfo
+			dflag <- lift GHC.getSessionDynFlags
+			return $ Declaration
+				(GHC.getOccString n)
+				Nothing
+				Nothing
+				(fromMaybe (Function Nothing []) (tyResult >>= showResult dflag))
+		showResult :: GHC.DynFlags -> GHC.TyThing -> Maybe DeclarationInfo
+		showResult dflags (GHC.AnId i) = Just $ Function (Just $ 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
+			tcon
+				| GHC.isAlgTyCon t && not (GHC.isNewTyCon t) && not (GHC.isClassTyCon t) = Data
+				| GHC.isNewTyCon t = NewType
+				| GHC.isClassTyCon t = Class
+				| GHC.isSynTyCon t = Type
+				| otherwise = Type
+
+withInitializedPackages :: [String] -> (GHC.DynFlags -> GHC.Ghc a) -> IO a
+withInitializedPackages ghcOpts cont = GHC.runGhc (Just GHC.libdir) $ do
+		fs <- GHC.getSessionDynFlags
+		GHC.defaultCleanupHandler fs $ do
+			(fs', _, _) <- GHC.parseDynamicFlags fs (map GHC.noLoc ghcOpts)
+			GHC.setSessionDynFlags fs'
+			(result, _) <- GHC.liftIO $ GHC.initPackages fs'
+			cont result
+
+withPackages :: [String] -> (GHC.DynFlags -> ErrorT String GHC.Ghc a) -> ErrorT String IO a
+withPackages ghcOpts cont = ErrorT $ withInitializedPackages ghcOpts (runErrorT . cont)
+
+withPackages_ :: [String] -> ErrorT String GHC.Ghc a -> ErrorT String IO a
+withPackages_ ghcOpts act = withPackages ghcOpts (const act)
+
+inOtherModuleSource :: GHC.Name -> GHC.Ghc (Maybe GHC.TyThing)
+inOtherModuleSource nm = GHC.getModuleInfo (GHC.nameModule nm) >> GHC.lookupGlobalName nm
+
+formatType :: GHC.NamedThing a => GHC.DynFlags -> (a -> GHC.Type) -> a -> String
+formatType dflag f x = showOutputable dflag (removeForAlls $ f x)
+
+removeForAlls :: GHC.Type -> GHC.Type
+removeForAlls ty = removeForAlls' ty' tty' where
+	ty'  = GHC.dropForAlls ty
+	tty' = GHC.splitFunTy_maybe ty'
+
+removeForAlls' :: GHC.Type -> Maybe (GHC.Type, GHC.Type) -> GHC.Type
+removeForAlls' ty Nothing = ty
+removeForAlls' ty (Just (pre, ftype))
+	| GHC.isPredTy pre = GHC.mkFunTy pre (GHC.dropForAlls ftype)
+	| otherwise = ty
+
+showOutputable :: GHC.Outputable a => GHC.DynFlags -> a -> String
+showOutputable dflag = unwords . lines . showUnqualifiedPage dflag . GHC.ppr
+
+showUnqualifiedPage :: GHC.DynFlags -> GHC.SDoc -> String
+showUnqualifiedPage dflag = Pretty.showDoc Pretty.LeftMode 0 . GHC.withPprStyleDoc dflag styleUnqualified
+
+styleUnqualified :: GHC.PprStyle
+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)
+ src/HsDev/Symbols.hs view
@@ -0,0 +1,531 @@+{-# LANGUAGE  TypeSynonymInstances, FlexibleInstances, OverloadedStrings #-}
+{-# OPTIONS_GHC -fno-warn-orphans #-}
+
+module HsDev.Symbols (
+	-- * Information
+	Import(..),
+	Symbol(..),
+	ModuleId(..), Module(..), moduleLocals, moduleLocalDeclarations, moduleModuleDeclarations, moduleId,
+	Locals(..),
+	Declaration(..), declarationLocals,
+	TypeInfo(..),
+	DeclarationInfo(..),
+	ModuleDeclaration(..),
+	Inspection(..), inspectionOpts,
+	Inspected(..), InspectedModule,
+
+	-- * Functions
+	showTypeInfo,
+	declarationInfo, declarationTypeInfo, declarationTypeCtor, declarationTypeName,
+	qualifiedName,
+	importQualifier,
+
+	-- * Utility
+	Canonicalize(..),
+	locateProject,
+	locateSourceDir,
+
+	-- * Modifiers
+	addDeclaration,
+
+	-- * Other
+	unalias, moduleContents,
+
+	-- * Reexports
+	module HsDev.Symbols.Class,
+	module HsDev.Symbols.Documented
+	) where
+
+import Control.Applicative
+import Control.Arrow
+import Control.DeepSeq (NFData(..))
+import Control.Monad.Trans.Maybe
+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.Time.Clock.POSIX (POSIXTime)
+import Data.Foldable (Foldable(..))
+import Data.Traversable (Traversable(..))
+import System.Directory
+import System.FilePath
+
+import HsDev.Symbols.Class
+import HsDev.Symbols.Documented (Documented(..))
+import HsDev.Project
+import HsDev.Util (tab, tabs, (.::))
+
+-- | Module import
+data Import = Import {
+	importModuleName :: String,
+	importIsQualified :: Bool,
+	importAs :: Maybe String,
+	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
+
+instance Show Import where
+	show i = "import " ++ (if importIsQualified i then "qualified " else "") ++ importModuleName i ++ maybe "" (" as " ++) (importAs i)
+
+instance ToJSON Import where
+	toJSON i = object [
+		"name" .= importModuleName i,
+		"qualified" .= importIsQualified i,
+		"as" .= importAs i,
+		"pos" .= importPosition i]
+
+instance FromJSON Import where
+	parseJSON = withObject "import" $ \v -> Import <$>
+		v .:: "name" <*>
+		v .:: "qualified" <*>
+		v .:: "as" <*>
+		v .:: "pos"
+
+-- | Imported module can be accessed via qualifier
+importQualifier :: Maybe String -> Import -> Bool
+importQualifier Nothing i
+	| not (importIsQualified i) = True
+	| otherwise = False
+importQualifier (Just q) i
+	| q == importModuleName i = True
+	| Just q == importAs i = True
+	| otherwise = False
+
+instance Symbol Module where
+	symbolName = moduleName
+	symbolQualifiedName = moduleName
+	symbolDocs = moduleDocs
+	symbolLocation m = Location (moduleLocation m) Nothing
+
+instance Symbol ModuleId where
+	symbolName = moduleIdName
+	symbolQualifiedName = moduleIdName
+	symbolDocs = const Nothing
+	symbolLocation m = Location (moduleIdLocation m) Nothing
+
+instance Symbol Declaration where
+	symbolName = declarationName
+	symbolQualifiedName = declarationName
+	symbolDocs = declarationDocs
+	symbolLocation d = Location (OtherModuleSource Nothing) (declarationPosition d)
+
+instance Symbol ModuleDeclaration where
+	symbolName = declarationName . moduleDeclaration
+	symbolQualifiedName d = qualifiedName (declarationModuleId d) (moduleDeclaration d)
+	symbolDocs = declarationDocs . moduleDeclaration
+	symbolLocation d = (symbolLocation $ declarationModuleId d) {
+		locationPosition = declarationPosition $ moduleDeclaration d }
+
+-- | Module id
+data ModuleId = ModuleId {
+	moduleIdName :: String,
+	moduleIdLocation :: ModuleLocation }
+		deriving (Eq, Ord)
+
+instance NFData ModuleId where
+	rnf (ModuleId n l) = rnf n `seq` rnf l
+
+instance Show ModuleId where
+	show (ModuleId n l) = "module " ++ n ++ " from " ++ show l
+
+instance ToJSON ModuleId where
+	toJSON m = object [
+		"name" .= moduleIdName m,
+		"location" .= moduleIdLocation m]
+
+instance FromJSON ModuleId where
+	parseJSON = withObject "module id" $ \v -> ModuleId <$>
+		v .:: "name" <*>
+		v .:: "location"
+
+-- | Module
+data Module = Module {
+	moduleName :: String,
+	moduleDocs :: Maybe String,
+	moduleLocation :: ModuleLocation,
+	moduleExports :: [String],
+	moduleImports :: [Import],
+	moduleDeclarations :: Map String Declaration }
+		deriving (Ord)
+
+instance ToJSON Module where
+	toJSON m = object [
+		"name" .= moduleName m,
+		"docs" .= moduleDocs m,
+		"location" .= moduleLocation m,
+		"exports" .= moduleExports m,
+		"imports" .= moduleImports m,
+		"declarations" .= M.elems (moduleDeclarations m)]
+
+instance FromJSON Module where
+	parseJSON = withObject "module" $ \v -> Module <$>
+		v .:: "name" <*>
+		v .:: "docs" <*>
+		v .:: "location" <*>
+		v .:: "exports" <*>
+		v .:: "imports" <*>
+		((M.fromList . map (declarationName &&& id)) <$>v .:: "declarations")
+
+instance NFData Module where
+	rnf (Module n d s e i ds) = rnf n `seq` rnf d `seq` rnf s `seq` rnf e `seq` rnf i `seq` rnf ds
+
+instance Eq Module where
+	l == r = moduleName l == moduleName r && moduleLocation l == moduleLocation r
+
+instance Show Module where
+	show m = unlines $ filter (not . null) [
+		"module " ++ moduleName m,
+		"\tlocation: " ++ show (moduleLocation m),
+		"\texports: " ++ intercalate ", " (moduleExports m),
+		"\timports:",
+		unlines $ map (tab 2 . show) $ moduleImports m,
+		"\tdeclarations:",
+		unlines $ map (tabs 2 . show) $ M.elems (moduleDeclarations m),
+		maybe "" ("\tdocs: " ++) (moduleDocs m)]
+
+-- | Bring locals to top
+moduleLocals :: Module -> Module
+moduleLocals m = m { moduleDeclarations = moduleLocalDeclarations m }
+
+-- | Get declarations with locals
+moduleLocalDeclarations :: Module -> Map String Declaration
+moduleLocalDeclarations =
+	M.fromList .
+	map (declarationName &&& id) . 
+	concatMap declarationLocals' .
+	M.elems .
+	moduleDeclarations
+	where
+		declarationLocals' :: Declaration -> [Declaration]
+		declarationLocals' d = d : declarationLocals d
+
+-- | Get list of declarations as ModuleDeclaration
+moduleModuleDeclarations :: Module -> [ModuleDeclaration]
+moduleModuleDeclarations m = [ModuleDeclaration (moduleId m) d | d <- M.elems (moduleDeclarations m)]
+
+-- Make ModuleId by Module
+moduleId :: Module -> ModuleId
+moduleId m = ModuleId {
+	moduleIdName = moduleName m,
+	moduleIdLocation = moduleLocation m }
+
+class Locals a where
+	locals :: a -> [Declaration]
+	where_ :: a -> [Declaration] -> a
+
+-- | Declaration
+data Declaration = Declaration {
+	declarationName :: String,
+	declarationDocs :: Maybe String,
+	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
+
+instance Show Declaration where
+	show d = unlines $ filter (not . null) [
+		brief d,
+		maybe "" ("\tdocs: " ++) $ declarationDocs d,
+		maybe "" (("\tlocation: " ++ ) . show) $ declarationPosition d]
+
+instance ToJSON Declaration where
+	toJSON d = object [
+		"name" .= declarationName d,
+		"docs" .= declarationDocs d,
+		"pos" .= declarationPosition d,
+		"decl" .= declaration d]
+
+instance FromJSON Declaration where
+	parseJSON = withObject "declaration" $ \v -> Declaration <$>
+		v .:: "name" <*>
+		v .:: "docs" <*>
+		v .:: "pos" <*>
+		v .:: "decl"
+
+instance Locals Declaration where
+	locals = locals . declaration
+	where_ d ds = d { declaration = where_ (declaration d) ds }
+
+declarationLocals :: Declaration -> [Declaration]
+declarationLocals d = map prefix' $ locals $ declaration d where
+	prefix' decl = decl { declarationName = declarationName decl }
+
+-- | Common info for type/newtype/data/class
+data TypeInfo = TypeInfo {
+	typeInfoContext :: Maybe String,
+	typeInfoArgs :: [String],
+	typeInfoDefinition :: Maybe String }
+		deriving (Eq, Ord, Read, Show)
+
+instance NFData TypeInfo where
+	rnf (TypeInfo c a d) = rnf c `seq` rnf a `seq` rnf d
+
+instance ToJSON TypeInfo where
+	toJSON t = object [
+		"ctx" .= typeInfoContext t,
+		"args" .= typeInfoArgs t,
+		"def" .= typeInfoDefinition t]
+
+instance FromJSON TypeInfo where
+	parseJSON = withObject "type info" $ \v -> TypeInfo <$>
+		v .:: "ctx" <*>
+		v .:: "args" <*>
+		v .:: "def"
+
+showTypeInfo :: TypeInfo -> String -> String -> String
+showTypeInfo ti pre name = pre ++ maybe "" (++ " =>") (typeInfoContext ti) ++ " " ++ name ++ " " ++ unwords (typeInfoArgs ti) ++ maybe "" (" = " ++) (typeInfoDefinition ti)
+
+-- | Declaration info
+data DeclarationInfo =
+	Function { functionType :: Maybe String, localDeclarations :: [Declaration] } |
+	Type { typeInfo :: TypeInfo } |
+	NewType { newTypeInfo :: TypeInfo } |
+	Data { dataInfo :: TypeInfo } |
+	Class { classInfo :: TypeInfo }
+		deriving (Ord)
+
+instance NFData DeclarationInfo where
+	rnf (Function f ds) = rnf f `seq` rnf ds
+	rnf (Type i) = rnf i
+	rnf (NewType i) = rnf i
+	rnf (Data i) = rnf i
+	rnf (Class i) = rnf i
+
+instance Eq DeclarationInfo where
+	(Function l lds) == (Function r rds) = l == r && lds == rds
+	(Type _) == (Type _) = True
+	(NewType _) == (NewType _) = True
+	(Data _) == (Data _) = True
+	(Class _) == (Class _) = True
+	_ == _ = False
+
+instance ToJSON DeclarationInfo where
+	toJSON i = case declarationInfo i of
+		Left (t, ds) -> object ["what" .= ("function" :: String), "type" .= t, "locals" .= ds]
+		Right ti -> object ["what" .= declarationTypeName i, "info" .= ti]
+
+instance FromJSON DeclarationInfo where
+	parseJSON = withObject "declaration info" $ \v -> do
+		w <- fmap (id :: String -> String) $ v .:: "what"
+		if w == "function"
+			then Function <$> v .:: "type" <*> v .:: "locals"
+			else declarationTypeCtor w <$> v .:: "info"
+
+instance Locals DeclarationInfo where
+	locals (Function _ ds) = ds
+	locals _ = []
+	where_ (Function n s) ds = Function n (s ++ ds)
+	where_ d _ = d
+
+-- | Get function type of type info
+declarationInfo :: DeclarationInfo -> Either (Maybe String, [Declaration]) TypeInfo
+declarationInfo (Function t ds) = Left (t, ds)
+declarationInfo (Type ti) = Right ti
+declarationInfo (NewType ti) = Right ti
+declarationInfo (Data ti) = Right ti
+declarationInfo (Class ti) = Right ti
+
+-- | Get type info of declaration
+declarationTypeInfo :: DeclarationInfo -> Maybe TypeInfo
+declarationTypeInfo = either (const Nothing) Just . declarationInfo
+
+declarationTypeCtor :: String -> TypeInfo -> DeclarationInfo
+declarationTypeCtor "type" = Type
+declarationTypeCtor "newtype" = NewType
+declarationTypeCtor "data" = Data
+declarationTypeCtor "class" = Class
+declarationTypeCtor "" = error "Invalid type constructor name"
+
+declarationTypeName :: DeclarationInfo -> Maybe String
+declarationTypeName (Type _) = Just "type"
+declarationTypeName (NewType _) = Just "newtype"
+declarationTypeName (Data _) = Just "data"
+declarationTypeName (Class _) = Just "class"
+declarationTypeName _ = Nothing
+
+-- | Symbol in module
+data ModuleDeclaration = ModuleDeclaration {
+	declarationModuleId :: ModuleId,
+	moduleDeclaration :: Declaration }
+		deriving (Eq, Ord)
+
+instance NFData ModuleDeclaration where
+	rnf (ModuleDeclaration m s) = rnf m `seq` rnf s
+
+instance Show ModuleDeclaration where
+	show (ModuleDeclaration m s) = unlines $ filter (not . null) [
+		show s,
+		"\tmodule: " ++ show (moduleIdLocation m)]
+
+instance ToJSON ModuleDeclaration where
+	toJSON d = object [
+		"module-id" .= declarationModuleId d,
+		"declaration" .= moduleDeclaration d]
+
+instance FromJSON ModuleDeclaration where
+	parseJSON = withObject "module declaration" $ \v -> ModuleDeclaration <$>
+		v .:: "module-id" <*>
+		v .:: "declaration"
+
+-- | Returns qualified name of symbol
+qualifiedName :: ModuleId -> Declaration -> String
+qualifiedName m d = moduleIdName m ++ "." ++ declarationName d
+
+class Canonicalize a where
+	canonicalize :: a -> IO a
+
+instance Canonicalize Cabal where
+	canonicalize Cabal = return Cabal
+	canonicalize (Sandbox p) = fmap Sandbox $ canonicalizePath p
+
+instance Canonicalize Project where
+	canonicalize (Project nm p c desc) = liftM3 (Project nm) (canonicalizePath p) (canonicalizePath c) (return desc)
+
+instance Canonicalize ModuleLocation where
+	canonicalize (FileModule f p) = liftM2 FileModule (canonicalizePath f) (traverse canonicalize p)
+	canonicalize (CabalModule c p n) = fmap (\c' -> CabalModule c' p n) $ canonicalize c
+	canonicalize (OtherModuleSource m) = return $ OtherModuleSource m
+
+-- | Find project file is related to
+locateProject :: FilePath -> IO (Maybe Project)
+locateProject file = do
+	file' <- canonicalizePath file
+	isDir <- doesDirectoryExist file'
+	if isDir then locateHere file' else locateParent (takeDirectory file')
+	where
+		locateHere path = do
+			cts <- filter (not . null . takeBaseName) <$> getDirectoryContents path
+			return $ fmap (project . (path </>)) $ find ((== ".cabal") . takeExtension) cts
+		locateParent dir = do
+			cts <- filter (not . null . takeBaseName) <$> getDirectoryContents dir
+			case find ((== ".cabal") . takeExtension) cts of
+				Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir)
+				Just cabalf -> return $ Just $ project (dir </> cabalf)
+
+-- | Locate source dir of file
+locateSourceDir :: FilePath -> IO (Maybe FilePath)
+locateSourceDir f = runMaybeT $ do
+	file <- liftIO $ canonicalizePath f
+	p <- MaybeT $ locateProject file
+	proj <- MaybeT $ fmap (either (const Nothing) Just) $ runErrorT $ loadProject p
+	MaybeT $ return $ findSourceDir proj file
+
+-- | Add declaration to module
+addDeclaration :: Declaration -> Module -> Module
+addDeclaration decl m = m { moduleDeclarations = decls' } where
+	decls' = M.insert (declarationName decl) decl $ moduleDeclarations m
+
+-- | Unalias import name
+unalias :: Module -> String -> [String]
+unalias m alias = [importModuleName i | i <- moduleImports m, importAs i == Just alias]
+
+instance Documented ModuleId where
+	brief m = moduleIdName m ++ " in " ++ show (moduleIdLocation m)
+
+instance Documented Module where
+	brief m = moduleName m ++ " in " ++ show (moduleLocation m)
+	detailed m = unlines $ header ++ docs ++ cts where
+		header = [brief m, ""]
+		docs = maybe [] return $ moduleDocs m
+		cts = moduleContents m
+
+instance Documented Declaration where
+	brief d = case declarationInfo $ declaration d of
+		Left (f, _) -> name ++ maybe "" (" :: " ++) f
+		Right ti -> showTypeInfo ti (fromMaybe err $ declarationTypeName $ declaration d) name
+		where
+			name = declarationName d
+			err = error "Impossible happened: declarationTypeName"
+
+instance Documented ModuleDeclaration where
+	brief = brief . moduleDeclaration
+
+-- | Module contents
+moduleContents :: Module -> [String]
+moduleContents = map showDecl . M.elems . moduleDeclarations where
+	showDecl d = brief d ++ maybe "" (" -- " ++) (declarationDocs d)
+
+-- | Inspection data
+data Inspection =
+	-- | No inspection
+	InspectionNone |
+	-- | Time and flags of inspection
+	InspectionAt POSIXTime [String]
+		deriving (Eq, Ord)
+
+-- | Get inspection opts
+inspectionOpts :: Inspection -> [String]
+inspectionOpts InspectionNone = []
+inspectionOpts (InspectionAt _ opts) = opts
+
+instance NFData Inspection where
+	rnf InspectionNone = ()
+	rnf (InspectionAt t fs) = rnf t `seq` rnf fs
+
+instance Show Inspection where
+	show InspectionNone = "none"
+	show (InspectionAt tm fs) = "mtime " ++ show tm ++ ", flags [" ++ intercalate ", " fs ++ "]"
+
+instance Read POSIXTime where
+	readsPrec i = map (first (fromIntegral :: Integer -> POSIXTime)) . readsPrec i
+
+instance ToJSON Inspection where
+	toJSON InspectionNone = object ["inspected" .= False]
+	toJSON (InspectionAt tm fs) = object [
+		"mtime" .= (floor tm :: Integer),
+		"flags" .= fs]
+
+instance FromJSON Inspection where
+	parseJSON = withObject "inspection" $ \v ->
+		((const InspectionNone :: Bool -> Inspection) <$> v .:: "inspected") <|>
+		(InspectionAt <$> (fromInteger <$> v .:: "mtime") <*> (v .:: "flags"))
+
+-- | Inspected entity
+data Inspected i a = Inspected {
+	inspection :: Inspection,
+	inspectedId :: i,
+	inspectionResult :: Either String a }
+		deriving (Eq, Ord)
+
+instance Functor (Inspected i) where
+	fmap f insp = insp {
+		inspectionResult = fmap f (inspectionResult insp) }
+
+instance Foldable (Inspected i) where
+	foldMap f = either mempty f . inspectionResult
+
+instance Traversable (Inspected i) where
+	traverse f (Inspected insp i r) = Inspected insp i <$> either (pure . Left) (liftA Right . f) r
+
+instance (NFData i, NFData a) => NFData (Inspected i a) where
+	rnf (Inspected t i r) = rnf t `seq` rnf i `seq` rnf r
+
+-- | Inspected module
+type InspectedModule = Inspected ModuleLocation Module
+
+instance Show InspectedModule where
+	show (Inspected i mi m) = unlines [either showError show m, "\tinspected: " ++ show i] where
+		showError :: String -> String
+		showError e = unlines $ ("\terror: " ++ e) : case mi of
+			FileModule f p -> ["file: " ++ f, "project: " ++ maybe "" projectPath p]
+			CabalModule c p n -> ["cabal: " ++ show c, "package: " ++ maybe "" show p, "name: " ++ n]
+			OtherModuleSource src -> ["source: " ++ fromMaybe "" src]
+
+instance ToJSON InspectedModule where
+	toJSON im = object [
+		"inspection" .= inspection im,
+		"location" .= inspectedId im,
+		either ("error" .=) ("module" .=) (inspectionResult im)]
+
+instance FromJSON InspectedModule where
+	parseJSON = withObject "inspected module" $ \v -> Inspected <$>
+		v .:: "inspection" <*>
+		v .:: "location" <*>
+		((Left <$> v .:: "error") <|> (Right <$> v .:: "module"))
+ src/HsDev/Symbols/Class.hs view
@@ -0,0 +1,17 @@+module HsDev.Symbols.Class (
+	Symbol(..),
+	symbolModuleLocation,
+
+	module HsDev.Symbols.Location
+	) where
+
+import HsDev.Symbols.Location
+
+class Symbol a where
+	symbolName :: a -> String
+	symbolQualifiedName :: a -> String
+	symbolDocs :: a -> Maybe String
+	symbolLocation :: a -> Location
+
+symbolModuleLocation :: Symbol a => a -> ModuleLocation
+symbolModuleLocation = locationModule . symbolLocation
+ src/HsDev/Symbols/Documented.hs view
@@ -0,0 +1,22 @@+module HsDev.Symbols.Documented (
+	Documented(..),
+	defaultDetailed
+	) where
+
+import HsDev.Symbols.Class
+
+-- | Documented symbol
+class Symbol a => Documented a where
+	brief :: a -> String
+	detailed :: a -> String
+	detailed = unlines . defaultDetailed
+
+-- | Default detailed docs
+defaultDetailed :: Documented a => a -> [String]
+defaultDetailed s = header ++ docs ++ loc where
+	header = [brief s, ""]
+	docs = maybe [] return $ symbolDocs s
+	loc
+		| null mloc = []
+		| otherwise = ["Defined at " ++ mloc]
+	mloc = show (symbolLocation s)
+ src/HsDev/Symbols/Location.hs view
@@ -0,0 +1,176 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Symbols.Location (
+	ModulePackage(..), ModuleLocation(..), moduleSource, moduleCabalPackage,
+	Position(..), Region(..), region, regionLines, regionStr,
+	Location(..),
+
+	packageOpt,
+
+	module HsDev.Cabal
+	) where
+
+import Control.Applicative
+import Control.DeepSeq (NFData(..))
+import Control.Monad (join)
+import Data.Aeson
+import Data.Char (isSpace, isDigit)
+import Data.List (intercalate)
+import Data.Maybe
+import Text.Read (readMaybe)
+
+import HsDev.Cabal
+import HsDev.Project
+import HsDev.Util ((.::))
+
+data ModulePackage = ModulePackage {
+	packageName :: String,
+	packageVersion :: String }
+		deriving (Eq, Ord)
+
+instance NFData ModulePackage where
+	rnf (ModulePackage n v) = rnf n `seq` rnf v
+
+instance Show ModulePackage where
+	show (ModulePackage n "") = n
+	show (ModulePackage n v) = n ++ "-" ++ v
+
+instance Read ModulePackage where
+	readsPrec _ str = case pkg of
+		"" -> []
+		_ -> [(ModulePackage n v, str')]
+		where
+			(pkg, str') = break isSpace str
+			(rv, rn) = span versionChar $ reverse pkg
+			v = reverse rv
+			n = reverse $ dropWhile (== '-') rn
+
+			versionChar ch = isDigit ch || ch == '.'
+
+instance ToJSON ModulePackage where
+	toJSON (ModulePackage n v) = object [
+		"name" .= n,
+		"version" .= v]
+
+instance FromJSON ModulePackage where
+	parseJSON = withObject "module package" $ \v ->
+		ModulePackage <$> (v .:: "name") <*> (v .:: "version")
+
+-- | Location of module
+data ModuleLocation =
+	FileModule { moduleFile :: FilePath, moduleProject :: Maybe Project } |
+	CabalModule { moduleCabal :: Cabal, modulePackage :: Maybe ModulePackage, cabalModuleName :: String } |
+	OtherModuleSource { moduleOtherSource :: Maybe String }
+		deriving (Eq, Ord)
+
+moduleSource :: ModuleLocation -> Maybe FilePath
+moduleSource (FileModule f _) = Just f
+moduleSource _ = Nothing
+
+moduleCabalPackage :: ModuleLocation -> Maybe ModulePackage
+moduleCabalPackage (CabalModule _ p _) = p
+moduleCabalPackage _ = Nothing
+
+instance NFData ModuleLocation where
+	rnf (FileModule f p) = rnf f `seq` rnf p
+	rnf (CabalModule c p n) = rnf c `seq` rnf p `seq` rnf n
+	rnf (OtherModuleSource m) = rnf m
+
+instance Show ModuleLocation where
+	show (FileModule f p) = f ++ maybe "" (" in " ++) (fmap projectPath p)
+	show (CabalModule c p _) = show c ++ maybe "" (" in package " ++) (fmap show p)
+	show (OtherModuleSource m) = fromMaybe "" m
+
+instance ToJSON ModuleLocation where
+	toJSON (FileModule f p) = object ["file" .= f, "project" .= fmap projectCabal p]
+	toJSON (CabalModule c p n) = object ["cabal" .= c, "package" .= fmap show p, "name" .= n]
+	toJSON (OtherModuleSource s) = object ["source" .= s]
+
+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") <|>
+		(OtherModuleSource <$> v .:: "source")
+
+data Position = Position {
+	positionLine :: Int,
+	positionColumn :: Int }
+		deriving (Eq, Ord, Read)
+
+instance NFData Position where
+	rnf (Position l c) = rnf l `seq` rnf c
+
+instance Show Position where
+ 	show (Position l c) = show l ++ ":" ++ show c
+
+instance ToJSON Position where
+	toJSON (Position l c) = object [
+		"line" .= l,
+		"column" .= c]
+
+instance FromJSON Position where
+	parseJSON = withObject "position" $ \v -> Position <$>
+		v .:: "line" <*>
+		v .:: "column"
+
+data Region = Region {
+	regionFrom :: Position,
+	regionTo :: Position }
+		deriving (Eq, Ord, Read)
+
+region :: Position -> Position -> Region
+region f t = Region (min f t) (max f t)
+
+regionLines :: Region -> Int
+regionLines (Region f t) = succ $ positionLine t - positionLine f
+
+-- | Get string at region
+regionStr :: Region -> String -> String
+regionStr r@(Region f t) s = intercalate "\n" $ drop (pred $ positionColumn f) fline' : tl where
+	s' = take (regionLines r) $ drop (pred (positionLine f)) $ lines s
+	(fline:tl) = init s' ++ [take (pred $ positionColumn t) (last s')]
+	fline' = concatMap untab fline where
+		untab :: Char -> String
+		untab '\t' = replicate 8 ' '
+		untab ch = [ch]
+
+instance NFData Region where
+	rnf (Region f t) = rnf f `seq` rnf t
+
+instance Show Region where
+	show (Region f t) = show f ++ "-" ++ show t
+
+instance ToJSON Region where
+	toJSON (Region f t) = object [
+		"from" .= f,
+		"to" .= t]
+
+instance FromJSON Region where
+	parseJSON = withObject "region" $ \v -> Region <$>
+		v .:: "from" <*>
+		v .:: "to"
+
+-- | Location of symbol
+data Location = Location {
+	locationModule :: ModuleLocation,
+	locationPosition :: Maybe Position }
+		deriving (Eq, Ord)
+
+instance NFData Location where
+	rnf (Location m p) = rnf m `seq` rnf p
+
+instance Show Location where
+	show (Location m p) = show m ++ ":" ++ show p
+
+instance ToJSON Location where
+	toJSON (Location ml p) = object [
+		"module" .= ml,
+		"pos" .= p]
+
+instance FromJSON Location where
+	parseJSON = withObject "location" $ \v -> Location <$>
+		v .:: "module" <*>
+		v .:: "pos"
+
+packageOpt :: Maybe ModulePackage -> [String]
+packageOpt = maybeToList . fmap (("-package " ++) . packageName)
+ src/HsDev/Symbols/Util.hs view
@@ -0,0 +1,168 @@+module HsDev.Symbols.Util (
+	packageOf, projectOf,
+	inProject, inCabal, inPackage, inVersion, inFile, inModuleSource, inModule, byFile, byCabal, standalone,
+	imports, qualifier, imported, visible, inScope,
+	newestPackage,
+	sourceModule, visibleModule, preferredModule, uniqueModules,
+	allOf, anyOf
+	) where
+
+import Control.Arrow ((***), (&&&), second)
+import Control.Monad (join)
+import Data.Function (on)
+import Data.Maybe
+import Data.List (maximumBy, groupBy, sortBy, partition)
+import Data.Ord (comparing)
+import System.FilePath (normalise)
+
+import HsDev.Symbols
+import HsDev.Project
+
+-- | Get module project
+projectOf :: ModuleId -> Maybe Project
+projectOf m = case moduleIdLocation m of
+	FileModule _ proj -> proj
+	_ -> Nothing
+
+-- | Get module package
+packageOf :: ModuleId -> Maybe ModulePackage
+packageOf m = case moduleIdLocation m of
+	CabalModule _ package _ -> package
+	_ -> Nothing
+
+-- | Check if module in project
+inProject :: Project -> ModuleId -> Bool
+inProject p m = projectOf m == Just p
+
+-- | Check if module in cabal
+inCabal :: Cabal -> ModuleId -> Bool
+inCabal c m = case moduleIdLocation m of
+	CabalModule cabal _ _ -> cabal == c
+	_ -> False
+
+-- | Check if module in package
+inPackage :: String -> ModuleId -> Bool
+inPackage p m = case moduleIdLocation m of
+	CabalModule _ package _ -> Just p == fmap packageName package
+	_ -> False
+
+inVersion :: String -> ModuleId -> Bool
+inVersion v m = case moduleIdLocation m of
+	CabalModule _ package _ -> Just v == fmap packageVersion package
+	_ -> False
+
+-- | Check if module in file
+inFile :: FilePath -> ModuleId -> Bool
+inFile fpath m = case moduleIdLocation m of
+	FileModule f _ -> f == normalise fpath
+	_ -> False
+
+-- | Check if module in source
+inModuleSource :: Maybe String -> ModuleId -> Bool
+inModuleSource src m = case moduleIdLocation m of
+	OtherModuleSource src' -> src' == src
+	_ -> False
+
+-- | Check if declaration is in module
+inModule :: String -> ModuleId -> Bool
+inModule mname m = mname == moduleIdName m
+
+-- | Check if module defined in file
+byFile :: ModuleId -> Bool
+byFile m = case moduleIdLocation m of
+	FileModule _ _ -> True
+	_ -> False
+
+-- | Check if module got from cabal database
+byCabal :: ModuleId -> Bool
+byCabal m = case moduleIdLocation m of
+	CabalModule _ _ _ -> True
+	_ -> False
+
+-- | Check if module is standalone
+standalone :: ModuleId -> Bool
+standalone m = case moduleIdLocation m of
+	FileModule _ Nothing -> True
+	_ -> False
+
+-- | Get list of imports
+imports :: Module -> [Import]
+imports = moduleImports
+
+-- | 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)
+
+-- | Check if module imported via imports specified
+imported :: ModuleId -> [Import] -> Bool
+imported m = any (\i -> moduleIdName m == importModuleName i)
+
+-- | Check if module visible from this module within this project
+visible :: Project -> ModuleId -> ModuleId -> Bool
+visible p (ModuleId _ (FileModule src _)) m =
+	inProject p m || any (`inPackage` m) deps || maybe False ((`elem` deps) . projectName) (projectOf m)
+	where
+		deps = maybe [] infoDepends $ fileTarget p src
+visible _ _ _ = False
+
+-- | Check if module is in scope with qualifier
+inScope :: Module -> Maybe String -> ModuleId -> Bool
+inScope this q m = m `imported` qualifier this q
+
+-- | Select modules with last package version
+newestPackage :: Symbol a => [a] -> [a]
+newestPackage =
+	uncurry (++) .
+	(selectNewest *** map fst) .
+	partition (isJust . snd) .
+	map (id &&& (moduleNamePackage . symbolModuleLocation))
+	where
+		moduleNamePackage (CabalModule _ (Just p) mname) = Just (mname, p)
+		moduleNamePackage _ = Nothing
+		pname = fmap (second packageName) . snd
+		pver = fmap (packageVersion . snd) . snd
+		selectNewest :: Symbol a => [(a, Maybe (String, ModulePackage))] -> [a]
+		selectNewest =
+			map (fst . maximumBy (comparing pver)) .
+			groupBy ((==) `on` pname) .
+			sortBy (comparing pname)
+
+-- | Select module, defined by sources
+sourceModule :: Maybe Project -> [Module] -> Maybe Module
+sourceModule proj ms = listToMaybe $ maybe (const []) (filter . (. moduleId) . inProject) proj ms ++ filter (byFile . moduleId) ms
+
+-- | Select module, visible in project or cabal
+visibleModule :: Cabal -> Maybe Project -> [Module] -> Maybe Module
+visibleModule cabal proj ms = listToMaybe $ maybe (const []) (filter . (. moduleId) . inProject) proj ms ++ filter (inCabal cabal . moduleId) ms
+
+-- | Select preferred visible module
+preferredModule :: Cabal -> Maybe Project -> [ModuleId] -> Maybe ModuleId
+preferredModule cabal proj ms = listToMaybe $ concatMap (`filter` ms) order where
+	order = [
+		maybe (const False) inProject proj,
+		byFile,
+		inCabal cabal,
+		const True]
+
+-- | Remove duplicate modules, leave only `preferredModule`
+uniqueModules :: Cabal -> Maybe Project -> [ModuleId] -> [ModuleId]
+uniqueModules cabal proj =
+	catMaybes .
+	map (preferredModule cabal proj) .
+	groupBy ((==) `on` moduleIdName) .
+	sortBy (comparing moduleIdName)
+
+-- | Select value, satisfying to all predicates
+allOf :: [a -> Bool] -> a -> Bool
+allOf ps x = all ($ x) ps
+
+-- | Select value, satisfying one of predicates
+anyOf :: [a -> Bool] -> a -> Bool
+anyOf ps x = any ($ x) ps
+
+-- | Is file info actual?
+--isActual :: Symbol a -> IO Bool
+--isActual = maybe (return False) checkStamp . symbolLocation where
+--	checkStamp l = do
+--		actualStamp <- getModificationTime (locationFile l)
+--		return $ Just actualStamp == locationTimeStamp l
+ src/HsDev/Tools/Base.hs view
@@ -0,0 +1,74 @@+module HsDev.Tools.Base (
+	Result, ToolM,
+	runWait, runWait_,
+	tool, tool_,
+	match,
+	at,
+	inspect,
+	-- * Read parse utils
+	ReadM,
+	readParse, parseReads, parseRead
+	) where
+
+import Control.Monad.Error
+import Control.Monad.State
+import Data.Maybe (fromMaybe, listToMaybe)
+import System.Exit
+import System.Process
+import Text.RegexPR (matchRegexPR)
+
+import HsDev.Symbols
+import HsDev.Util (liftIOErrors)
+
+type Result = Either String String
+type ToolM a = ErrorT String IO a
+
+-- | Run command and wait for result
+runWait :: FilePath -> [String] -> String -> IO Result
+runWait name args input = do
+	(code, out, err) <- readProcessWithExitCode name args input
+	return $ if code == ExitSuccess && not (null out) then Right out else Left err
+
+-- | Run command with no input
+runWait_ :: FilePath -> [String] -> IO Result
+runWait_ name args = runWait name args ""
+
+-- | Tool
+tool :: FilePath -> [String] -> String -> ToolM String
+tool name args input = liftIOErrors $ ErrorT $ runWait name args input
+
+-- | Tool with no input
+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
+
+at :: (Int -> Maybe String) -> Int -> String
+at g i = fromMaybe (error $ "Can't find group " ++ show i) $ g i
+
+inspect :: Monad m => ModuleLocation -> ErrorT String m Inspection -> ErrorT String m Module -> ErrorT String m InspectedModule
+inspect mloc insp act = lift $ execStateT inspect' (Inspected InspectionNone mloc (Left "not inspected")) where
+	inspect' = runErrorT $ do
+		i <- mapErrorT lift insp
+		modify (\im -> im { inspection = i })
+		v <- mapErrorT lift act
+		modify (\im -> im { inspectionResult = Right v })
+		`catchError`
+		\e -> modify (\im -> im { inspectionResult = Left e })
+
+type ReadM a = StateT String [] a
+
+-- | Parse readable value
+readParse :: Read a => ReadM a
+readParse = StateT reads
+
+-- | Run parser
+parseReads :: String -> ReadM a -> [a]
+parseReads = flip evalStateT
+
+-- | Run parser and select first result
+parseRead :: String -> ReadM a -> Maybe a
+parseRead s = listToMaybe . parseReads s
+ src/HsDev/Tools/Cabal.hs view
@@ -0,0 +1,84 @@+{-# LANGUAGE OverloadedStrings, CPP #-}
+
+module HsDev.Tools.Cabal (
+	CabalPackage(..),
+	cabalList
+	) where
+
+import Control.Arrow
+import Control.Applicative
+import Control.Monad
+import Control.Monad.Error
+import Data.Aeson
+import Data.Char (isSpace)
+import Data.Maybe
+import Distribution.License
+import Distribution.Text
+import Distribution.Version
+
+import HsDev.Tools.Base
+import HsDev.Util
+
+data CabalPackage = CabalPackage {
+	cabalPackageName :: String,
+	cabalPackageSynopsis :: Maybe String,
+	cabalPackageDefaultVersion :: Maybe Version,
+	cabalPackageInstalledVersions :: [Version],
+	cabalPackageHomepage :: Maybe String,
+	cabalPackageLicense :: Maybe License }
+		deriving (Eq, Read, Show)
+
+instance ToJSON CabalPackage where
+	toJSON cp = object [
+		"name" .= cabalPackageName cp,
+		"synopsis" .= cabalPackageSynopsis cp,
+		"default-version" .= fmap display (cabalPackageDefaultVersion cp),
+		"installed-versions" .= map display (cabalPackageInstalledVersions cp),
+		"homepage" .= cabalPackageHomepage cp,
+		"license" .= fmap display (cabalPackageLicense cp)]
+
+instance FromJSON CabalPackage where
+	parseJSON = withObject "cabal-package" $ \v -> CabalPackage <$>
+		(v .:: "name") <*>
+		(v .:: "synopsis") <*>
+		((join . fmap simpleParse) <$> (v .:: "default-version")) <*>
+		((mapMaybe simpleParse) <$> (v .:: "installed-versions")) <*>
+		(v .:: "homepage") <*>
+		((join . fmap simpleParse) <$> (v .:: "license"))
+
+cabalList :: [String] -> ToolM [CabalPackage]
+cabalList queries = do
+#if mingw32_HOST_OS
+	rs <- liftM (split (all isSpace) . lines) $ tool_ "powershell" [
+		"-Command",
+		unwords (["&", "{", "chcp 65001 | out-null;", "cabal list"] ++ queries ++ ["}"])]
+#else
+	rs <- liftM (split (all isSpace) . lines) $ tool_ "cabal" ("list" : queries)
+#endif
+	return $ map toPackage $ mapMaybe parseFields rs
+	where
+		toPackage :: (String, [(String, String)]) -> CabalPackage
+		toPackage (name, fs) = CabalPackage {
+			cabalPackageName = name,
+			cabalPackageSynopsis = lookup "Synopsis" fs,
+			cabalPackageDefaultVersion = (lookup "Default available version" fs >>= simpleParse),
+			cabalPackageInstalledVersions = fromMaybe [] (lookup "Installed versions" fs >>= mapM (simpleParse . trim) . split (== ',')),
+			cabalPackageHomepage = lookup "Homepage" fs,
+			cabalPackageLicense = lookup "License" fs >>= simpleParse }
+
+		parseFields :: [String] -> Maybe (String, [(String, String)])
+		parseFields [] = Nothing
+		parseFields (('*':name):fs) = Just (trim name, mapMaybe parseField' fs) where
+			parseField' :: String -> Maybe (String, String)
+			parseField' str = case parseField str of
+				(fname, Just fval) -> Just (fname, fval)
+				_ -> Nothing
+		parseFields _ = Nothing
+
+		-- foo: bar → (foo, bar)
+		parseField :: String -> (String, Maybe String)
+		parseField = (trim *** (parseValue . trim . drop 1)) . break (== ':')
+		-- [ ... ] → Nothing, ... → Just ...
+		parseValue :: String -> Maybe String
+		parseValue ('[':_) = Nothing
+		parseValue v = Just v
+ src/HsDev/Tools/ClearImports.hs view
@@ -0,0 +1,101 @@+module HsDev.Tools.ClearImports (
+	dumpMinimalImports, waitImports, cleanTmpImports,
+	findMinimalImports,
+	groupImports, splitImport,
+	clearImports
+	) where
+
+import Control.Arrow
+import Control.Concurrent (threadDelay)
+import Control.Exception
+import Control.Monad.Error
+import Data.Char
+import Data.List
+import System.Directory
+import System.FilePath
+import qualified Language.Haskell.Exts as Exts
+
+import GHC
+import GHC.Paths (libdir)
+
+-- | Dump minimal imports
+dumpMinimalImports :: [String] -> FilePath -> ErrorT String IO String
+dumpMinimalImports opts f = do
+	cur <- liftIO getCurrentDirectory
+	file <- liftIO $ canonicalizePath f
+
+	m <- liftIO $ Exts.parseFile file
+	mname <- case m of
+		Exts.ParseFailed loc err -> throwError $
+			"Failed to parse file at " ++
+			Exts.prettyPrint loc ++ ":" ++ err
+		Exts.ParseOk (Exts.Module _ (Exts.ModuleName mname) _ _ _ _ _) -> return mname
+
+	ErrorT $ handle onError $ liftM Right $ void $ liftIO $ runGhc (Just libdir) $ do
+		df <- getSessionDynFlags
+		let
+			df' = df {
+				ghcLink = NoLink,
+				hscTarget = HscNothing,
+				dumpDir = Just cur,
+				stubDir = Just cur,
+				objectDir = Just cur,
+				hiDir = Just cur }
+		(df'', _, _) <- parseDynamicFlags df' (map noLoc ("-ddump-minimal-imports" : opts))
+		setSessionDynFlags df''
+		defaultCleanupHandler df'' $ do
+			t <- guessTarget file Nothing
+			setTargets [t]
+			load LoadAllTargets
+
+	length mname `seq` return mname
+	where
+		onError :: SomeException -> IO (Either String ())
+		onError = return . Left . show
+
+-- | Read imports from file
+waitImports :: FilePath -> IO [String]
+waitImports f = retry 1000 $ do
+	is <- liftM lines $ readFile f
+	length is `seq` return is
+
+-- | Clean temporary files
+cleanTmpImports :: FilePath -> IO ()
+cleanTmpImports dir = do
+	dumps <- liftM (map (dir </>) . filter ((== ".imports") . takeExtension)) $ getDirectoryContents dir
+	forM_ dumps $ handle ignoreIO . retry 1000 . removeFile
+	where
+		ignoreIO :: IOException -> IO ()
+		ignoreIO _ = return ()
+
+-- | Dump and read imports
+findMinimalImports :: [String] -> FilePath -> ErrorT String IO [String]
+findMinimalImports opts f = do
+	file <- liftIO $ canonicalizePath f
+	mname <- dumpMinimalImports opts file
+	is <- liftIO $ waitImports (mname <.> "imports")
+	tmp <- liftIO getCurrentDirectory
+	liftIO $ cleanTmpImports tmp
+	return is
+
+-- | Groups several lines related to one import by indents
+groupImports :: [String] -> [[String]]
+groupImports = unfoldr getPack where
+	getPack [] = Nothing
+	getPack (s:ss) = Just $ first (s:) $ break (null . takeWhile isSpace) ss
+
+-- | Split import to import and import-list
+splitImport :: [String] -> (String, String)
+splitImport = splitBraces . unwords . map trim where
+	trim = twice $ reverse . dropWhile isSpace
+	cut = twice $ reverse . drop 1
+	twice f = f . f
+	splitBraces = (trim *** (trim . cut)) . break (== '(')
+
+-- | Returns minimal imports for file specified
+clearImports :: [String] -> FilePath -> ErrorT String IO [(String, String)]
+clearImports opts = liftM (map splitImport . groupImports) . findMinimalImports opts
+
+-- | Retry action on fail
+retry :: (MonadPlus m, MonadIO m) => Int -> m a -> m a
+retry dt act = msum $ act : repeat ((liftIO (threadDelay dt) >>) act)
+ src/HsDev/Tools/GhcMod.hs view
@@ -0,0 +1,153 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Tools.GhcMod (
+	list,
+	browse, browseInspection,
+	info,
+	TypedRegion(..),
+	typeOf
+	) where
+
+import Control.Applicative
+import Control.Arrow
+import Control.DeepSeq
+import Control.Exception
+import Control.Monad.Error
+import Data.Aeson
+import Data.Char
+import Data.Maybe
+import Data.List (sort, nub)
+import qualified Data.Map as M
+import Exception (ghandle)
+import GHC (Ghc, runGhc, getSessionDynFlags, defaultCleanupHandler)
+import GHC.Paths (libdir)
+import Text.Read (readMaybe)
+import System.Directory (getCurrentDirectory)
+
+import qualified Language.Haskell.GhcMod as GhcMod
+
+import HsDev.Cabal
+import HsDev.Project
+import HsDev.Symbols
+import HsDev.Symbols.Location
+import HsDev.Tools.Base
+import HsDev.Util ((.::))
+
+list :: [String] -> Cabal -> ErrorT String IO [ModuleLocation]
+list opts cabal = do
+	cradle <- liftIO $ cradle cabal Nothing
+	ms <- tryGhc $ GhcMod.listMods (GhcMod.defaultOptions { GhcMod.ghcOpts = opts }) cradle
+	return [CabalModule cabal (readMaybe p) m | (m, p) <- ms]
+
+browse :: [String] -> Cabal -> String -> Maybe ModulePackage -> ErrorT String IO InspectedModule
+browse opts cabal mname mpackage = inspect mloc (return $ browseInspection opts) $ do
+	cradle <- liftIO $ cradle cabal Nothing
+	ts <- tryGhc $ GhcMod.browse (GhcMod.defaultOptions { GhcMod.detailed = True, GhcMod.ghcOpts = packageOpt mpackage ++ opts, GhcMod.packageId = mpackagename }) cradle mname
+	return $ Module {
+		moduleName = mname,
+		moduleDocs = Nothing,
+		moduleLocation = mloc,
+		moduleExports = [],
+		moduleImports = [],
+		moduleDeclarations = decls ts }
+	where
+		mpackagename = fmap packageName 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
+			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
+
+browseInspection :: [String] -> Inspection
+browseInspection = InspectionAt 0
+
+info :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> String -> ErrorT String IO Declaration
+info opts cabal file mproj mname sname = do
+	cradle <- liftIO $ cradle cabal mproj
+	rs <- tryGhc $ GhcMod.info (GhcMod.defaultOptions { GhcMod.ghcOpts = cabalOpt cabal ++ opts }) cradle file mname sname
+	toDecl rs
+	where
+		toDecl s = maybe (throwError $ "Can't parse info: '" ++ s ++ "'") return $ parseData s `mplus` parseFunction s
+		parseFunction s = do
+			groups <- match (sname ++ "\\s+::\\s+(.*?)(\\s+--(.*))?$") s
+			return $ Declaration sname Nothing Nothing (Function (Just $ groups `at` 1) [])
+		parseData s = do
+			groups <- match "(newtype|type|data)\\s+((.*)=>\\s+)?(\\S+)\\s+((\\w+\\s+)*)=(\\s*(.*)\\s+-- Defined)?" 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)
+		trim = p . p where
+			p = reverse . dropWhile isSpace
+
+data TypedRegion = TypedRegion {
+	typedRegion :: Region,
+	typedExpr :: String,
+	typedType :: String }
+		deriving (Eq, Ord, Read, Show)
+
+instance NFData TypedRegion where
+	rnf (TypedRegion r e t) = rnf r `seq` rnf e `seq` rnf t
+
+instance ToJSON TypedRegion where
+	toJSON (TypedRegion r e t) = object [
+		"region" .= r,
+		"expr" .= e,
+		"type" .= t]
+
+instance FromJSON TypedRegion where
+	parseJSON = withObject "typed region" $ \v -> TypedRegion <$>
+		v .:: "region" <*>
+		v .:: "expr" <*>
+		v .:: "type"
+
+typeOf :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> Int -> Int -> ErrorT String IO [TypedRegion]
+typeOf opts cabal file mproj mname line col = do
+	fileCts <- liftIO $ readFile file
+	cradle <- liftIO $ cradle cabal mproj
+	ts <- fmap lines $ tryGhc $ GhcMod.typeOf (GhcMod.defaultOptions { GhcMod.ghcOpts = cabalOpt cabal ++ opts }) cradle file mname line col
+	return $ mapMaybe (toRegionType fileCts) ts
+	where
+		toRegionType :: String -> String -> Maybe TypedRegion
+		toRegionType fstr s = do
+			(r, tp) <- parseRead s $ (,) <$> parseRegion <*> readParse
+			return $ TypedRegion r (regionStr r fstr) tp
+		parseRegion :: ReadM Region
+		parseRegion = Region <$> parsePosition <*> parsePosition
+		parsePosition :: ReadM Position
+		parsePosition = Position <$> readParse <*> readParse
+
+tryGhc :: Ghc a -> ErrorT String IO a
+tryGhc act = ErrorT $ ghandle rethrow $ liftM Right $ runGhc (Just libdir) $ do
+	dflags <- getSessionDynFlags
+	defaultCleanupHandler dflags act
+	where
+		rethrow :: SomeException -> IO (Either String a)
+		rethrow = return . Left . show
+
+cradle :: Cabal -> Maybe Project -> IO GhcMod.Cradle
+cradle cabal Nothing = do
+	dir <- getCurrentDirectory
+	return $ GhcMod.Cradle dir dir Nothing (sandbox cabal) []
+cradle cabal (Just proj) = do
+	dir <- getCurrentDirectory
+	return $ GhcMod.Cradle
+		dir
+		(projectPath proj)
+		(Just $ projectCabal proj)
+		(sandbox cabal)
+		(zip deps (repeat Nothing))
+	where
+		deps = fromMaybe [] $ do
+			desc <- projectDescription proj
+			return $ nub $ sort $ concatMap infoDepends $ concat [
+					map buildInfo (maybeToList (projectLibrary desc)),
+					map buildInfo (projectExecutables desc),
+					map buildInfo (projectTests desc)]
+ src/HsDev/Tools/GhcMod/InferType.hs view
@@ -0,0 +1,43 @@+module HsDev.Tools.GhcMod.InferType (
+	untyped, inferType, inferTypes
+	) where
+
+import Control.Monad.Error
+import Data.Traversable (traverse)
+
+import HsDev.Cabal
+import HsDev.Project
+import HsDev.Symbols
+import HsDev.Tools.GhcMod
+
+-- | Is declaration untyped
+untyped :: DeclarationInfo -> Bool
+untyped (Function Nothing _) = True
+untyped _ = False
+
+-- | Infer type of declaration
+inferType :: [String] -> Cabal -> FilePath -> Maybe Project -> String -> Declaration -> ErrorT String IO Declaration
+inferType opts cabal src mproj mname 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) }
+
+		setType :: DeclarationInfo -> Maybe String -> DeclarationInfo
+		setType (Function _ ds) newType = Function newType ds
+		setType info _ = info
+
+		getType :: DeclarationInfo -> Maybe String
+		getType (Function fType _) = fType
+		getType _ = Nothing
+
+-- | Infer types for module
+inferTypes :: [String] -> Cabal -> Module -> ErrorT String 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
+		return m { moduleDeclarations = inferredDecls }
+	_ -> throwError "Type infer works only for source files"
+ src/HsDev/Tools/HDocs.hs view
@@ -0,0 +1,50 @@+module HsDev.Tools.HDocs (
+	hdocs, hdocsCabal,
+	setDocs,
+	loadDocs,
+
+	hdocsProcess
+	) where
+
+import Control.Exception
+import Control.Monad ()
+import Control.Monad.Error
+
+import Data.Aeson (decode)
+import qualified Data.ByteString.Lazy.Char8 as L (pack)
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Maybe ()
+import System.Process (readProcess)
+
+import qualified HDocs.Module as HDocs
+import qualified HDocs.Haddock as HDocs ()
+
+import HsDev.Symbols
+
+-- | Get docs for module
+hdocs :: String -> [String] -> IO (Map String String)
+hdocs mname opts = do
+	ds <- runErrorT $ HDocs.moduleDocs opts mname
+	return $ either (const M.empty) HDocs.formatDocs ds
+
+-- | Get all docs
+hdocsCabal :: Cabal -> [String] -> ErrorT String IO (Map String (Map String String))
+hdocsCabal cabal opts = liftM (M.map HDocs.formatDocs) $ HDocs.installedDocs (cabalOpt cabal ++ opts)
+
+-- | 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 }
+
+-- | Load docs for module
+loadDocs :: [String] -> Module -> IO Module
+loadDocs opts m = do
+	d <- hdocs (moduleName m) opts
+	return $ setDocs d m
+
+hdocsProcess :: String -> [String] -> IO (Maybe (Map String String))
+hdocsProcess mname opts = handle onErr $ liftM (decode . L.pack) $ readProcess "hdocs" opts' "" where
+	opts' = mname : concat [["-g", opt] | opt <- opts]
+	onErr :: SomeException -> IO (Maybe a)
+	onErr _ = return Nothing
+ src/HsDev/Tools/Hayoo.hs view
@@ -0,0 +1,138 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module HsDev.Tools.Hayoo (
+	-- * Types
+	HayooResult(..), HayooFunction(..), HayooCompletion(..), HayooName(..),
+	hayooAsDeclaration,
+	-- * Search help online
+	hayoo,
+	-- * Utils
+	untagDescription
+	) where
+
+import Control.Arrow
+import Control.Applicative
+import Control.Monad.Error
+
+import Data.Aeson
+import qualified Data.ByteString.Lazy.Char8 as L
+import qualified Data.Text.Lazy as T
+import qualified Data.Text.Lazy.Encoding as T
+import Network.HTTP
+import Text.RegexPR (gsubRegexPR)
+
+import HsDev.Symbols
+import HsDev.Symbols.Documented
+import HsDev.Util
+
+-- | Hayoo response
+data HayooResult = HayooResult {
+	hayooMessage :: String,
+	hayooHits :: Int,
+	hayooFunctions :: [HayooFunction],
+	hayooCompletions :: [HayooCompletion],
+	hayooModules :: [HayooName],
+	hayooPackages :: [HayooName] }
+		deriving (Eq, Ord, Read, Show)
+
+instance FromJSON HayooResult where
+	parseJSON = withObject "hayoo response" $ \v -> HayooResult <$>
+		(v .:: "message") <*>
+		(v .:: "hits") <*>
+		(v .:: "functions") <*>
+		(v .:: "completions") <*>
+		(v .:: "modules") <*>
+		(v .:: "packages")
+
+-- | Hayoo function information
+data HayooFunction = HayooFunction {
+	hayooName :: String,
+	hayooSignature :: String,
+	hayooModule :: String,
+	hayooPackage :: String,
+	hayooHackage :: String,
+	hayooDescription :: String }
+		deriving (Eq, Ord, Read, Show)
+
+instance Symbol HayooFunction where
+	symbolName = hayooName
+	symbolQualifiedName f = hayooModule f ++ "." ++ hayooName f
+	symbolDocs = Just . hayooDescription
+	symbolLocation r = Location (OtherModuleSource $ Just $ hayooHackage r) Nothing where
+
+instance Documented HayooFunction where
+	brief f
+		| hayooSignature f `elem` ["type", "newtype", "data", "class"] =
+			hayooSignature f ++ " " ++ hayooName f
+		| otherwise = hayooName f ++ " :: " ++ hayooSignature f
+	detailed f = unlines $ defaultDetailed f ++ online where
+		online = [
+			"", "Hayoo online documentation", "",
+			"Package: " ++ hayooPackage f,
+			"Hackage URL: " ++ hayooHackage f]
+
+instance FromJSON HayooFunction where
+	parseJSON = withObject "function" $ \v -> HayooFunction <$>
+		(v .:: "name") <*>
+		(v .:: "signature") <*>
+		(v .:: "module") <*>
+		(v .:: "package") <*>
+		(v .:: "uri") <*>
+		(v .:: "description")
+
+-- | Hayoo completions
+data HayooCompletion = HayooCompletion {
+	completionWord :: String,
+	completionCount :: Int }
+		deriving (Eq, Ord, Read, Show)
+
+instance FromJSON HayooCompletion where
+	parseJSON = withObject "completion" $ \v -> HayooCompletion <$>
+		(v .:: "word") <*>
+		(v .:: "count")
+
+-- | Hayoo name
+data HayooName = HayooName {
+	nameName :: String,
+	nameCount :: Int }
+		deriving (Eq, Ord, Read, Show)
+
+instance FromJSON HayooName where
+	parseJSON = withObject "name" $ \v -> HayooName <$>
+		(v .:: "name") <*>
+		(v .:: "count")
+
+-- | 'HayooFunction' as 'Declaration'
+hayooAsDeclaration :: HayooFunction -> ModuleDeclaration
+hayooAsDeclaration f = ModuleDeclaration {
+	declarationModuleId = ModuleId {
+		moduleIdName = hayooModule f,
+		moduleIdLocation = OtherModuleSource (Just $ hayooHackage f) },
+	moduleDeclaration = Declaration {
+		declarationName = hayooName f,
+		declarationDocs = Just (addOnline $ untagDescription $ hayooDescription f),
+		declarationPosition = Nothing,
+		declaration = declInfo } }
+	where
+		-- Add other info
+		addOnline d = unlines $ [
+			d, "",
+			"Hayoo online documentation",
+			"",
+			"Package: " ++ hayooPackage f,
+			"Hackage URL: " ++ hayooHackage f]
+
+		declInfo
+			| hayooSignature f `elem` ["type", "newtype", "data", "class"]
+				= declarationTypeCtor (hayooSignature f) $ TypeInfo Nothing [] Nothing
+			| otherwise = Function (Just $ hayooSignature f) []
+
+-- | Search hayoo
+hayoo :: String -> ErrorT String IO HayooResult
+hayoo q = do
+	resp <- ErrorT $ fmap (show +++ rspBody) $ simpleHTTP (getRequest $ "http://holumbus.fh-wedel.de/hayoo/hayoo.json?query=" ++ urlEncode q)
+	ErrorT $ return $ eitherDecode $ L.pack resp
+
+-- | Remove tags in description
+untagDescription :: String -> String
+untagDescription = gsubRegexPR "</?\\w+[^>]*>" ""
+ src/HsDev/Util.hs view
@@ -0,0 +1,110 @@+module HsDev.Util (
+	directoryContents,
+	traverseDirectory,
+	isParent,
+	haskellSource,
+	cabalFile,
+	-- * String utils
+	tab, tabs, trim, split,
+	-- * Helper
+	(.::),
+	-- * Exceptions
+	liftException, liftExceptionM, liftIOErrors,
+	-- * UTF-8
+	fromUtf8, toUtf8
+	) where
+
+import Control.Arrow (second)
+import Control.Exception
+import Control.Monad
+import Control.Monad.Error
+import qualified Control.Monad.CatchIO as C
+import Data.Aeson
+import Data.Aeson.Types (Parser)
+import Data.Char (isSpace)
+import Data.List (isPrefixOf, unfoldr)
+import qualified Data.HashMap.Strict as HM (HashMap, toList)
+import Data.ByteString.Lazy (ByteString)
+import Data.Text (Text)
+import qualified Data.Text.Lazy as T
+import qualified Data.Text.Lazy.Encoding as T
+import System.Directory
+import System.FilePath
+
+-- | Get directory contents safely
+directoryContents :: FilePath -> IO [FilePath]
+directoryContents p = handle ignore $ do
+	b <- doesDirectoryExist p
+	if b
+		then liftM (map (p </>) . filter (`notElem` [".", ".."])) (getDirectoryContents p)
+		else return []
+	where
+		ignore :: SomeException -> IO [FilePath]
+		ignore _ = return []
+
+-- | Collect all file names in directory recursively
+traverseDirectory :: FilePath -> IO [FilePath]
+traverseDirectory path = handle onError $ do
+	cts <- directoryContents path
+	liftM concat $ forM cts $ \c -> do
+		isDir <- doesDirectoryExist c
+		if isDir
+			then traverseDirectory c
+			else return [c]
+	where
+		onError :: IOException -> IO [FilePath]
+		onError _ = return []
+
+-- | Is one path parent of another
+isParent :: FilePath -> FilePath -> Bool
+isParent dir file = norm dir `isPrefixOf` norm file where
+	norm = splitDirectories . normalise
+
+-- | Is haskell source?
+haskellSource :: FilePath -> Bool
+haskellSource f = takeExtension f `elem` [".hs", ".lhs"]
+
+-- | Is cabal file?
+cabalFile :: FilePath -> Bool
+cabalFile f = takeExtension f == ".cabal"
+
+-- | Add N tabs to line
+tab :: Int -> String -> String
+tab n s = replicate n '\t' ++ s
+
+-- | Add N tabs to multiline
+tabs :: Int -> String -> String
+tabs n = unlines . map (tab n) . lines
+
+-- | Trim string
+trim :: String -> String
+trim = p . p where
+	p = reverse . dropWhile isSpace
+
+-- | Split list
+split :: (a -> Bool) -> [a] -> [[a]]
+split p = takeWhile (not . null) . unfoldr (Just . second (drop 1) . break p)
+
+-- | Workaround, sometimes we get HM.lookup "foo" v == Nothing, but lookup "foo" (HM.toList v) == Just smth
+(.::) :: FromJSON a => HM.HashMap Text Value -> Text -> Parser a
+v .:: name = maybe (fail $ "key " ++ show name ++ " not present") parseJSON $ lookup name $ HM.toList v
+
+-- | 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)
+
+-- | Lift IO exception to MonadError
+liftExceptionM :: (C.MonadCatchIO m, Error e, MonadError e m) => m a -> m a
+liftExceptionM act = C.catch act onError where
+	onError = throwError . strMsg . (show :: SomeException -> String)
+
+-- | Lift IO exceptions to ErrorT
+liftIOErrors :: C.MonadCatchIO m => ErrorT String m a -> ErrorT String m a
+liftIOErrors act = liftException (runErrorT act) >>= either throwError return
+
+fromUtf8 :: ByteString -> String
+fromUtf8 = T.unpack . T.decodeUtf8
+
+toUtf8 :: String -> ByteString
+toUtf8 = T.encodeUtf8 . T.pack
+ src/System/Win32/PowerShell.hs view
@@ -0,0 +1,193 @@+{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, OverlappingInstances, GeneralizedNewtypeDeriving #-}
+
+module System.Win32.PowerShell (
+	-- * Run PowerShell
+	ps,
+	-- * PowerShell functions
+	startProcess, codepage, outNull,
+	-- * PowerShell monad
+	PS(..), seqPS, emit, emit_, (=:), invoke,
+	-- * Expression
+	Args(..), CmdLet(..), Expr(..), compile,
+	raw, name, lit, var, bra, (.=), flag, named, call, cmdlet, invoke, lambda, (.|), foreach, filter,
+	-- * Convertible
+	ToPS(..),
+	-- * Escape functions
+	translate, translateArg
+	) where
+
+import Prelude hiding (filter)
+
+import Control.Applicative (Applicative(..))
+import Control.Monad
+import Control.Monad.Writer
+import Data.Char (isAlphaNum)
+import Data.List (intercalate)
+import Data.Map (Map)
+import qualified Data.Map as M
+
+import HsDev.Tools.Base (ToolM, tool_)
+
+ps :: PS a -> ToolM String
+ps script = tool_ "powershell" ["-Command", compile $ seqPS script]
+
+-- start-process
+startProcess :: String -> [String] -> CmdLet
+startProcess n as = cmdlet "start-process" [name n, lit $ intercalate ", " (map translateArg as)] [named "WindowStyle" $ lit "Hidden"]
+
+-- chcp
+codepage :: Int -> CmdLet
+codepage n = cmdlet "chcp" [lit n] []
+
+-- out-null
+outNull :: CmdLet
+outNull = cmdlet "out-null" [] []
+
+newtype PS a = PS { unPS :: Writer [Expr] a }
+	deriving (Applicative, Functor, Monad, MonadWriter [Expr])
+
+seqPS :: PS a -> Expr
+seqPS (PS act) = case execWriter act of
+	[] -> error "No expressions"
+	es -> foldr1 Sequence es
+
+emit :: Expr -> PS Expr
+emit e = PS $ tell [e] >> return e
+
+emit_ :: Expr -> PS ()
+emit_ = void . emit
+
+infixr 6 =:
+(=:) :: String -> Expr -> PS Expr
+name =: expr = emit_ (name .= expr) >> return (var name)
+
+-- | Invoke cmdlet
+invoke :: CmdLet -> PS ()
+invoke = emit_ . Invoke
+
+-- | Positional and named args
+data Args = Args [Expr] (Map String (Maybe Expr))
+
+instance Monoid Args where
+	mempty = Args [] M.empty
+	mappend (Args lp ln) (Args rp rn) = Args (lp ++ rp) (M.union ln rn)
+
+-- | Call to cmdlet
+data CmdLet = CmdLet {
+	cmdLet :: Expr,
+	cmdArgs :: Args }
+
+-- | Expression
+data Expr =
+	Emit String |
+	-- ^ native expression
+	Literal String |
+	-- ^ literal
+	Var String |
+	-- ^ $name
+	Bracket Expr |
+	-- ^ (expr)
+	Assign [String] [Expr] |
+	-- ^ $x, $y, ... = expr1, expr2, ...
+	Invoke CmdLet |
+	-- ^ cmd args... named...
+	Lambda [String] ([Expr] -> Expr) |
+	-- ^ { param($...); expr }
+	Sequence Expr Expr |
+	-- ^ expr1; expr2
+	Pipe Expr CmdLet
+	-- ^ expr | cmd
+
+compile :: Expr -> String
+compile (Emit s) = s
+compile (Literal l) = l
+compile (Var v) = '$':v
+compile (Bracket e) = "(" ++ compile e ++ ")"
+compile (Assign vs es) = intercalate ", " (map ('$':) vs) ++ " = " ++ intercalate ", " (map (compile . bra) es)
+compile (Invoke (CmdLet e (Args ps ns))) = unwords [invoke', ps', ns'] where
+	invoke' = case e of
+		Var n -> n
+		_ -> unwords ["&", compile $ bra e]
+	ps' = intercalate " " $ map (compile . bra) ps
+	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]
+-- { param($File); $args[0]; }
+compile (Lambda ns body) = unwords ["{", param', body', "}"] where
+	param' = "param(" ++ intercalate "," ns ++ ");"
+	body' = compile $ body (map var ns)
+compile (Sequence l r) = compile l ++ "; " ++ compile r
+compile (Pipe e c) = compile e ++ " | " ++ compile (Invoke c)
+
+raw :: String -> Expr
+raw = Emit
+
+name :: String -> Expr
+name = Literal
+
+lit :: ToPS a => a -> Expr
+lit = Literal . toPS
+
+var :: String -> Expr
+var = Var
+
+bra :: Expr -> Expr
+bra = Bracket
+
+infixr 6 .= 
+(.=) :: String -> Expr -> Expr
+v .= e = Assign [v] [e]
+
+flag :: String -> (String, Maybe Expr)
+flag n = (n, Nothing)
+
+named :: String -> Expr -> (String, Maybe Expr)
+named n e = (n, Just e)
+
+call :: Expr -> [Expr] -> [(String, Maybe Expr)] -> CmdLet
+call f pos named = CmdLet f $ Args pos (M.fromList named)
+
+cmdlet :: String -> [Expr] -> [(String, Maybe Expr)] -> CmdLet
+cmdlet n pos = call (name n) pos
+
+lambda :: [String] -> ([Expr] -> Expr) -> Expr
+lambda = Lambda
+
+infixr 6 .|
+(.|) :: Expr -> CmdLet -> Expr
+e .| c = Pipe e c
+
+foreach :: (Expr -> Expr) -> CmdLet
+foreach f = cmdlet "%" [f $ var "_"] []
+
+filter :: (Expr -> Expr) -> CmdLet
+filter p = cmdlet "?" [p $ var "_"] []
+
+class ToPS a where
+	toPS :: a -> String
+
+instance ToPS Int where
+	toPS = show
+
+instance ToPS String where
+	toPS = translate
+
+instance ToPS Bool where
+	toPS True = "$true"
+	toPS False = "$false"
+
+instance ToPS a => ToPS [a] where
+	toPS = intercalate ", " . map toPS
+
+translate :: String -> String
+translate str = '"' : snd (foldr escape (True, "\"") str) where
+	escape '"' (b, str) = (True, '\\' : '"' : str)
+	escape '\\' (True, str) = (True, '\\' : '\\' : str)
+	escape '\\' (False, str) = (False, '\\' : str)
+	escape c (b, str) = (False, c : str)
+
+translateArg :: String -> String
+translateArg str
+	| all isAlphaNum str = str
+	| otherwise = "'" ++ translate str ++ "'"
+ tests/Test.hs view
@@ -0,0 +1,6 @@+module Main (
+	main
+	) where
+
+main :: IO ()
+main = return ()
+ tools/Commands.hs view
@@ -0,0 +1,981 @@+{-# LANGUAGE OverloadedStrings, CPP #-}
+
+module Commands (
+	mainCommands, commands
+	) where
+
+import Control.Applicative
+import Control.Arrow
+import Control.Monad
+import Control.Monad.Error
+import Control.Monad.Trans.Maybe
+import Control.Exception
+import Control.Concurrent
+import Data.Aeson
+import Data.Aeson.Encode.Pretty
+import Data.Char
+import Data.Either
+import Data.List
+import Data.Maybe
+import Data.Monoid
+import qualified Data.ByteString.Char8 as B
+import Data.ByteString.Lazy.Char8 (ByteString)
+import qualified Data.ByteString.Lazy.Char8 as L
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Traversable (traverse)
+import Network.Socket
+import System.Directory
+import System.Environment
+import System.Exit
+import System.IO
+import System.Process
+import System.Console.GetOpt
+import System.Timeout
+import System.FilePath
+import Text.Read (readMaybe)
+
+import qualified HsDev.Database.Async as DB
+import HsDev.Commands
+import HsDev.Database
+import HsDev.Project
+import HsDev.Symbols
+import HsDev.Symbols.Util
+import HsDev.Util
+import HsDev.Scan
+import qualified HsDev.Tools.Cabal as Cabal
+import qualified HsDev.Tools.GhcMod as GhcMod (typeOf)
+import qualified HsDev.Tools.Hayoo as Hayoo
+import qualified HsDev.Cache.Structured as SC
+import HsDev.Cache
+
+import qualified Control.Concurrent.FiniteChan as F
+import System.Command
+
+#if mingw32_HOST_OS
+import System.Win32.FileMapping.Memory (withMapFile, readMapFile)
+import System.Win32.FileMapping.NamePool
+#else
+import System.Posix.Process
+import System.Posix.IO
+#endif
+
+import qualified Update
+import Types
+
+#if mingw32_HOST_OS
+
+translate :: String -> String
+translate str = '"' : snd (foldr escape (True,"\"") str) where
+	escape '"'  (b, str) = (True,  '\\' : '"'  : str)
+	escape '\\' (True, str) = (True,  '\\' : '\\' : str)
+	escape '\\' (False, str) = (False, '\\' : str)
+	escape c (b, str) = (False, c : str)
+
+powershell :: String -> String
+powershell str
+	| all isAlphaNum str = str
+	| otherwise = "'" ++ translate str ++ "'"
+
+#endif
+
+-- | Main commands
+mainCommands :: [Command (IO ())]
+mainCommands = addHelp "hsdev" id $ srvCmds ++ map wrapCmd commands where
+	wrapCmd :: Command CommandAction -> Command (IO ())
+	wrapCmd = fmap sendCmd . addClientOpts . fmap withOptsArgs
+	srvCmds = [
+		cmd_ ["run"] [] "run interactive" runi',
+		cmd ["server", "start"] [] "start remote server" serverOpts start',
+		cmd ["server", "run"] [] "start server" serverOpts run',
+		cmd ["server", "stop"] [] "stop remote server" clientOpts stop']
+
+	runi' _ = do
+		dir <- getCurrentDirectory
+		db <- DB.newAsync
+		forever $ do
+			s <- getLine
+			processCmd (CommandOptions db (const $ return ()) (const $ return Nothing) dir putStrLn getLine (error "Not supported") exitSuccess) 1000 s (L.putStrLn . encode)
+	start' sopts _ = do
+#if mingw32_HOST_OS
+		let
+			args = ["server", "run"] ++ serverOptsToArgs sopts
+		myExe <- getExecutablePath
+		r <- readProcess "powershell" [
+			"-Command",
+			unwords [
+				"&", "{", "start-process",
+				powershell myExe,
+				intercalate ", " (map powershell args),
+				"-WindowStyle Hidden",
+				"}"]] ""
+		if all isSpace r
+			then putStrLn $ "Server started at port " ++ show (fromJust $ getFirst $ serverPort sopts)
+			else putStrLn $ "Failed to start server: " ++ r
+#else
+		let
+			forkError :: SomeException -> IO ()
+			forkError e  = putStrLn $ "Failed to start server: " ++ show e
+
+			proxy :: IO ()
+			proxy = do
+				createSession
+				forkProcess serverAction
+				exitImmediately ExitSuccess
+
+			serverAction :: IO ()
+			serverAction = do
+				mapM_ closeFd [stdInput, stdOutput, stdError]
+				nullFd <- openFd "/dev/null" ReadWrite Nothing defaultFileFlags
+				mapM_ (dupTo nullFd) [stdInput, stdOutput, stdError]
+				closeFd nullFd
+				run' sopts []
+
+		handle forkError $ do
+			forkProcess proxy
+			putStrLn $ "Server started at port " ++ show (fromJust $ getFirst $ serverPort sopts)
+#endif
+	run' sopts _ = do
+		msgs <- F.newChan
+		outputDone <- newEmptyMVar
+		forkIO $ finally
+			(F.readChan msgs >>= mapM_ (logMsg sopts))
+			(putMVar outputDone ())
+
+		let
+			outputStr = F.putChan msgs
+			waitOutput = F.closeChan msgs >> takeMVar outputDone
+			withCache :: a -> (FilePath -> IO a) -> IO a
+			withCache v onCache = case getFirst (serverCache sopts) of
+				Nothing -> return v
+				Just cdir -> onCache cdir
+			writeCache :: Database -> IO ()
+			writeCache d = withCache () $ \cdir -> do
+				outputStr "writing cache"
+				SC.dump cdir (structurize d)
+			readCache :: (FilePath -> ErrorT String IO Structured) -> IO (Maybe Database)
+			readCache act = withCache Nothing $ join . liftM (either cacheErr cacheOk) . runErrorT . act where
+				cacheErr e = outputStr ("Unable read cache: " ++ e) >> return Nothing
+				cacheOk s = do
+					forM_ (M.keys (structuredCabals s)) $ \c -> outputStr ("cache read: cabal " ++ show c)
+					forM_ (M.keys (structuredProjects s)) $ \p -> outputStr ("cache read: project " ++ p)
+					case allModules (structuredFiles s) of
+						[] -> return ()
+						ms -> outputStr $ "cache read: " ++ show (length ms) ++ " files"
+					return $ Just $ merge s
+
+		outputStr $ "Server started at port " ++ show (fromJust $ getFirst $ serverPort sopts)
+
+		logIO "server exception: " outputStr $ flip finally waitOutput $ do
+			db <- DB.newAsync
+
+			when (getAny $ serverLoadCache sopts) $ withCache () $ \cdir -> do
+				outputStr $ "Loading cache from " ++ cdir
+				dbCache <- liftA merge <$> SC.load cdir
+				case dbCache of
+					Left err -> outputStr $ "Failed to load cache: " ++ err
+					Right dbCache' -> DB.update db (return dbCache')
+
+			waitListen <- newEmptyMVar
+			clientChan <- F.newChan
+
+			linkChan <- F.newChan
+			let
+				linkToSrv :: IO ()
+				linkToSrv = do
+					v <- newEmptyMVar
+					F.putChan linkChan (putMVar v ())
+					takeMVar v
+
+#if mingw32_HOST_OS
+			mmapPool <- createPool "hsdev"
+			let
+				-- | Send response as is or via memory mapped file
+				sendResponse :: Handle -> Response -> IO ()
+				sendResponse h r@(ResponseMapFile _) = L.hPutStrLn h $ encode r
+				sendResponse h r
+					| L.length msg <= 1024 = L.hPutStrLn h msg
+					| otherwise = do
+						sync <- newEmptyMVar
+						forkIO $ void $ withName mmapPool $ \mmapName -> do
+							runErrorT $ flip catchError
+								(\e -> liftIO $ do
+									sendResponse h $ Response $ object ["error" .= e]
+									putMVar sync ())
+								(withMapFile mmapName (L.toStrict msg) $ liftIO $ do
+									sendResponse h $ ResponseMapFile mmapName
+									putMVar sync ()
+									-- Dirty: give 10 seconds for client to read it
+									threadDelay 10000000)
+						takeMVar sync
+					where
+						msg = encode r
+#else
+			let
+				sendResponse h = L.hPutStrLn h . encode
+#endif
+
+			forkIO $ do
+				accepter <- myThreadId
+
+				let
+					serverStop = void $ forkIO $ do
+						void $ tryPutMVar waitListen ()
+						killThread accepter
+
+				s <- socket AF_INET Stream defaultProtocol
+				bind s (SockAddrInet (fromIntegral $ fromJust $ getFirst $ serverPort sopts) iNADDR_ANY)
+				listen s maxListenQueue
+				forever $ logIO "accept client exception: " outputStr $ do
+					s' <- fmap fst $ accept s
+					outputStr $ show s' ++ " connected"
+					void $ forkIO $ logIO (show s' ++ " exception: ") outputStr $
+						bracket (socketToHandle s' ReadWriteMode) hClose $ \h -> do
+							bracket newEmptyMVar (`putMVar` ()) $ \done -> do
+								me <- myThreadId
+								let
+									timeoutWait = do
+										notDone <- isEmptyMVar done
+										when notDone $ do
+											void $ forkIO $ do
+												threadDelay 10000000
+												tryPutMVar done ()
+												killThread me
+											void $ takeMVar done
+								F.putChan clientChan timeoutWait
+								req <- hGetLine' h
+								outputStr $ show s' ++ ": " ++ fromUtf8 req
+								case fmap extractCurrentDir $ eitherDecode req of
+									Left reqErr -> sendResponse h $ Response $ object [
+										"error" .= ("Invalid request" :: String),
+										"request" .= fromUtf8 req,
+										"what" .= reqErr]
+									Right (clientDir, reqArgs) -> processCmdArgs
+										(CommandOptions
+											db
+											writeCache
+											readCache
+											clientDir
+											outputStr
+											(fromUtf8 <$> hGetLine' h)
+											linkToSrv
+											serverStop)
+										(fromJust $ getFirst $ serverTimeout sopts) reqArgs (sendResponse h)
+								-- Send 'end' message and wait client
+								L.hPutStrLn h L.empty
+								outputStr $ "waiting " ++ show s'
+								ignoreIO $ void $ timeout 10000000 $ hGetLine' h
+								outputStr $ show s' ++ " disconnected"
+
+			takeMVar waitListen
+			withCache () $ \cdir -> do
+				outputStr $ "saving cache to " ++ cdir
+				logIO "cache saving exception: " outputStr $ do
+					dbval <- DB.readAsync db
+					SC.dump cdir $ structurize dbval
+				outputStr "cache saved"
+			outputStr "closing links"
+			F.stopChan linkChan >>= sequence_
+			outputStr "waiting for clients"
+			F.stopChan clientChan >>= sequence_
+			outputStr "server shutdown"
+	stop' copts _ = run (map wrapCmd' commands) onDef onError ["exit"] where
+		onDef = putStrLn "Command 'exit' not found"
+		onError es = putStrLn $ "Failed to stop server: " ++ intercalate ", " es
+		wrapCmd' = fmap (sendCmd . fmap ((,) copts) . withOptsArgs)
+
+	logIO :: String -> (String -> IO ()) -> IO () -> IO ()
+	logIO pre out act = handle onIO act where
+		onIO :: IOException -> IO ()
+		onIO e = out $ pre ++ show e
+
+	ignoreIO :: IO () -> IO ()
+	ignoreIO = handle (const (return ()) :: IOException -> IO ())
+
+	logMsg :: ServerOpts -> String -> IO ()
+	logMsg sopts s = ignoreIO $ do
+		putStrLn s
+		case getFirst (serverLog sopts) of
+			Nothing -> return ()
+			Just f -> withFile f AppendMode (`hPutStrLn` s)
+
+	-- Send command to server
+	sendCmd :: IO (ClientOpts, [String]) -> IO ()
+	sendCmd get' = do
+		svar <- newEmptyMVar
+		race [takeMVar svar, waitResponse >> putMVar svar ()]
+		where
+			waitResponse = do
+				curDir <- getCurrentDirectory
+				(p, as) <- get'
+				stdinData <- if getAny (clientData p)
+					then do
+						cdata <- liftM eitherDecode L.getContents
+						case cdata of
+							Left cdataErr -> do
+								putStrLn $ "Invalid data: " ++ cdataErr
+								exitFailure
+							Right dataValue -> return $ Just dataValue
+					else return Nothing
+
+				s <- socket AF_INET Stream defaultProtocol
+				addr' <- inet_addr "127.0.0.1"
+				connect s (SockAddrInet (fromIntegral $ fromJust $ getFirst $ clientPort p) addr')
+				h <- socketToHandle s ReadWriteMode
+				L.hPutStrLn h $ encode $ ["--current-directory=" ++ curDir] ++ setData stdinData as
+				responses <- liftM (takeWhile (not . L.null) . L.lines) $ L.hGetContents h
+				forM_ responses $
+					parseResponse >=>
+					(L.putStrLn . encodeValue (getAny $ clientPretty p))
+
+			setData :: Maybe ResultValue -> [String] -> [String]
+			setData Nothing = id
+			setData (Just d) = (++ ["--data=" ++ (fromUtf8 $ encode d)])
+
+			parseResponse r = fmap (either err' id) $ runErrorT $ do
+				v <- errT (eitherDecode r) `orFail` (\e -> "Can't decode response")
+				case v of
+					Response rv -> return rv
+					ResponseStatus sv -> return sv
+#if mingw32_HOST_OS
+					ResponseMapFile viewFile -> do
+						str <- fmap L.fromStrict (readMapFile viewFile) `orFail`
+							(\e -> "Can't read map view of file")
+						lift $ parseResponse str
+#else
+					ResponseMapFile viewFile -> return $ err' ("Not supported" :: String)
+#endif
+				where
+					errT act = ErrorT $ return act
+					orFail act msg = act `catchError` (throwError . msg)
+					err' msg = object ["error" .= msg]
+
+			encodeValue True = encodePretty
+			encodeValue False = encode
+
+	-- Add parsing 'ClieptOpts'
+	addClientOpts :: Command (IO [String]) -> Command (IO (ClientOpts, [String]))
+	addClientOpts c = c { commandRun = run' } where
+		run' args = fmap (fmap (fmap $ (,) p)) $ commandRun c args' where
+			(ps, args', _) = getOpt RequireOrder clientOpts args
+			p = mconcat ps `mappend` defaultConfig
+
+	extractCurrentDir :: [String] -> (FilePath, [String])
+	extractCurrentDir as = (head $ cur ++ ["."], as') where
+		(cur, as', _) = getOpt RequireOrder curDirOpts as
+		curDirOpts = [Option [] ["current-directory"] (ReqArg id "path") "current directory"]
+
+commands :: [Command CommandAction]
+commands = map wrapErrors $ map (fmap (fmap timeout')) cmds ++ map (fmap (fmap noTimeout)) linkCmd where
+	timeout' :: (CommandOptions -> IO CommandResult) -> (Int -> CommandOptions -> IO CommandResult)
+	timeout' f tm copts = fmap (fromMaybe $ err "timeout") $ timeout (tm * 1000) $ f copts
+	noTimeout :: (CommandOptions -> IO CommandResult) -> (Int -> CommandOptions -> IO CommandResult)
+	noTimeout f _ copts = f copts
+
+	handleErrors :: (Int -> CommandOptions -> IO CommandResult) -> (Int -> CommandOptions -> IO CommandResult)
+	handleErrors act tm copts = handle onCmdErr (act tm copts) where
+		onCmdErr :: SomeException -> IO CommandResult
+		onCmdErr = return . err . show
+
+	wrapErrors :: Command CommandAction -> Command CommandAction
+	wrapErrors = fmap (fmap handleErrors)
+
+	cmds = [
+		-- Ping command
+		cmd_' ["ping"] [] "ping server" ping',
+		-- Database commands
+		cmd' ["add"] [] "add info to database" [dataArg] add',
+		cmd' ["scan", "cabal"] [] "scan modules installed in cabal" [
+			sandbox, ghcOpts, wait, status] scanCabal',
+		cmd' ["scan", "module"] ["module name"] "scan module in cabal" [sandbox, ghcOpts] scanModule',
+		cmd' ["scan"] [] "scan sources" [
+			projectArg "project path or .cabal",
+			fileArg "source file",
+			pathArg "directory to scan for files and projects",
+			ghcOpts, wait, status] scan',
+		cmd' ["rescan"] [] "rescan sources" [
+			projectArg "project path or .cabal",
+			fileArg "source file",
+			pathArg "path to rescan",
+			ghcOpts, wait, status] rescan',
+		cmd' ["remove"] [] "remove modules info" [
+			sandbox,
+			projectArg "module project",
+			fileArg "module source file",
+			moduleArg,
+			packageArg, noLastArg, packageVersionArg,
+			allFlag] remove',
+		-- | Context free commands
+		cmd' ["list", "modules"] [] "list modules" [
+			projectArg "project to list modules from",
+			noLastArg,
+			packageArg,
+			sandbox, sourced, standaloned] listModules',
+		cmd_' ["list", "packages"] [] "list packages" listPackages',
+		cmd_' ["list", "projects"] [] "list projects" listProjects',
+		cmd' ["symbol"] ["name"] "get symbol info" (matches ++ [
+			projectArg "related project",
+			fileArg "source file",
+			moduleArg, localsArg,
+			packageArg, noLastArg, packageVersionArg,
+			sandbox, sourced, standaloned]) symbol',
+		cmd' ["module"] [] "get module info" [
+			moduleArg, localsArg,
+			packageArg, noLastArg, packageVersionArg,
+			projectArg "module project",
+			fileArg "module source file",
+			sandbox, sourced] modul',
+		cmd' ["project"] [] "get project info" [
+			projectArg "project path or name"] project',
+		-- Context commands
+		cmd' ["lookup"] ["symbol"] "lookup for symbol" ctx lookup',
+		cmd' ["whois"] ["symbol"] "get info for symbol" ctx whois',
+		cmd' ["scope", "modules"] [] "get modules accessible from module or within a project" ctx scopeModules',
+		cmd' ["scope"] [] "get declarations accessible from module or within a project" (ctx ++ matches ++ [globalArg]) scope',
+		cmd' ["complete"] ["input"] "show completions for input" ctx complete',
+		-- Tool commands
+		cmd' ["hayoo"] ["query"] "find declarations online via Hayoo" [] hayoo',
+		cmd' ["cabal", "list"] ["packages..."] "list cabal packages" [] cabalList',
+		cmd' ["ghc-mod", "type"] ["line", "column"] "infer type with 'ghc-mod type'" ctx ghcmodType',
+		-- Dump/load commands
+		cmd' ["dump", "cabal"] [] "dump cabal modules" [sandbox, cacheDir, cacheFile] dumpCabal',
+		cmd' ["dump", "projects"] [] "dump projects" [projectArg "project", cacheDir, cacheFile] dumpProjects',
+		cmd' ["dump", "files"] [] "dump standalone files" [cacheDir, cacheFile] dumpFiles',
+		cmd' ["dump"] [] "dump whole database" [cacheDir, cacheFile] dump',
+		cmd' ["load"] [] "load data" [cacheDir, cacheFile, dataArg, wait] load',
+		-- Exit
+		cmd_' ["exit"] [] "exit" exit']
+	linkCmd = [cmd' ["link"] [] "link to server" [] link']
+
+	-- Command arguments and flags
+	allFlag = option_ ['a'] "all" flag "remove all"
+	cacheDir = pathArg "cache path"
+	cacheFile = fileArg "cache file"
+	ctx = [fileArg "source file", sandbox]
+	dataArg = option_ [] "data" (req "contents") "data to pass to command"
+	fileArg = option_ ['f'] "file" (req "file")
+	findArg = option_ [] "find" (req "find") "infix match"
+	ghcOpts = option_ ['g'] "ghc" (req "ghc options") "options to pass to GHC"
+	globalArg = option_ [] "global" flag "scope of project"
+	localsArg = option_ ['l'] "locals" flag "look in local declarations"
+	noLastArg = option_ [] "no-last" flag "don't select last package version"
+	matches = [prefixArg, findArg]
+	moduleArg = option_ ['m'] "module" (req "module name") "module name"
+	packageArg = option_ [] "package" (req "package") "module package"
+	pathArg = option_ ['p'] "path" (req "path")
+	prefixArg = option_ [] "prefix" (req "prefix") "prefix match"
+	projectArg = option [] "project" ["proj"] (req "project")
+	packageVersionArg = option_ ['v'] "version" (req "version") "package version"
+	sandbox = option_ [] "sandbox" (noreq "path") "path to cabal sandbox"
+	sourced = option_ [] "src" flag "source files"
+	standaloned = option_ [] "stand" flag "standalone files"
+	status = option_ ['s'] "status" flag "show status of operation, works only with --wait"
+	wait = option_ ['w'] "wait" flag "wait for operation to complete"
+
+	-- ping server
+	ping' _ copts = return $ ResultOk $ ResultString "pong"
+	-- add data
+	add' as _ copts = do
+		dbval <- getDb copts
+		res <- runErrorT $ do
+			jsonData <- maybe (throwError $ err "Specify --data") return $ askOpt "data" as
+			decodedData <- either
+				(\err -> throwError (errArgs "Unable to decode data" [
+					("why", ResultString err),
+					("data", ResultString jsonData)]))
+				return $
+				eitherDecode $ toUtf8 jsonData
+			let
+				updateData (ResultDeclaration d) = throwError $ errArgs "Can't insert declaration" [("declaration", ResultDeclaration d)]
+				updateData (ResultModuleDeclaration md) = do
+					let
+						ModuleId mname mloc = declarationModuleId md
+						defMod = Module mname Nothing mloc [] mempty mempty
+						defInspMod = Inspected InspectionNone mloc (Right defMod)
+						dbmod = maybe
+							defInspMod
+							(\i -> i { inspectionResult = inspectionResult i <|> (Right defMod) }) $
+							M.lookup mloc (databaseModules dbval)
+						updatedMod = dbmod {
+							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)
+				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
+				updateData (ResultList l) = mapM_ updateData l
+				updateData (ResultMap m) = mapM_ updateData $ M.elems m
+				updateData (ResultString s) = throwError $ err "Can't insert string"
+				updateData ResultNone = return ()
+			updateData decodedData
+		return $ either id (const (ResultOk ResultNone)) res
+	-- scan
+	scan' as _ copts = updateProcess copts as $
+		mapM_ (\(n, f) -> forM_ (askOpts n as) (canonicalizePath' copts >=> f (getGhcOpts as))) [
+			("project", Update.scanProject),
+			("file", Update.scanFile),
+			("path", Update.scanDirectory)]
+	-- scan cabal
+	scanCabal' as _ copts = error_ $ do
+		cabal <- getCabal copts as
+		lift $ updateProcess copts as $ Update.scanCabal (getGhcOpts as) cabal
+	-- scan cabal module
+	scanModule' as [] copts = return $ err "Module name not specified"
+	scanModule' as ms copts = error_ $ do
+		cabal <- getCabal copts as
+		lift $ updateProcess copts as $
+			forM_ ms (Update.scanModule (getGhcOpts as) . CabalModule cabal Nothing)
+	-- rescan
+	rescan' as _ copts = do
+		dbval <- getDb copts
+		let
+			fileMap = M.fromList $ mapMaybe toPair $
+				selectModules (byFile . moduleId) dbval
+
+		(errors, filteredMods) <- liftM partitionEithers $ mapM runErrorT $ concat [
+			do
+				p <- askOpts "project" as
+				return $ do
+					p' <- getProject copts p
+					return $ M.fromList $ mapMaybe toPair $
+						selectModules (inProject p' . moduleId) dbval,
+			do
+				f <- askOpts "file" as
+				return $ maybe
+					(throwError $ "Unknown file: " ++ f)
+					(return . M.singleton f)
+					(lookupFile f dbval),
+			do
+				d <- askOpts "path" as
+				return $ return $ M.filterWithKey (\f _ -> isParent d f) fileMap]
+		let
+			rescanMods = map (getInspected dbval) $
+				M.elems $ if null filteredMods then fileMap else M.unions filteredMods
+
+		if not (null errors)
+			then return $ err $ intercalate ", " errors
+			else updateProcess copts as $ Update.runTask (toJSON $ ("rescanning modules" :: String)) $ do
+				needRescan <- Update.liftErrorT $ filterM (changedModule dbval (getGhcOpts as) . inspectedId) rescanMods
+				Update.scanModules (getGhcOpts as) (map (inspectedId &&& inspectionOpts . inspection) needRescan)
+	-- remove
+	remove' as _ copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		cabal <- askCabal copts as
+		proj <- askProject copts as
+		file <- traverse (canonicalizePath' copts) $ askOpt "file" as
+		let
+			cleanAll = hasOpt "all" as
+			filters = catMaybes [
+				fmap inProject proj,
+				fmap inFile file,
+				fmap inModule (askOpt "module" as),
+				fmap inPackage (askOpt "package" as),
+				fmap inVersion (askOpt "version" as),
+				fmap inCabal cabal]
+			toClean = newest as $ filter (allOf filters . moduleId) (allModules dbval)
+			action
+				| null filters && cleanAll = liftIO $ do
+					DB.modifyAsync (dbVar copts) DB.Clear
+					return ResultNone
+				| null filters && not cleanAll = throwError "Specify filter or explicitely set flag --all"
+				| cleanAll = throwError "--all flag can't be set with filters"
+				| otherwise = liftIO $ do
+					DB.modifyAsync (dbVar copts) $ DB.Remove $ mconcat $ map (fromModule . getInspected dbval) toClean
+					return $ ResultList $ map (ResultModuleId . moduleId) toClean
+		action
+	-- list modules
+	listModules' as _ copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		proj <- askProject copts as
+		cabal <- askCabal copts as
+		let
+			filters = allOf $ catMaybes [
+				fmap inProject proj,
+				fmap inPackage (askOpt "package" as),
+				fmap inCabal cabal,
+				if hasOpt "src" as then Just byFile else Nothing,
+				if hasOpt "stand" as then Just standalone else Nothing]
+		return $ ResultList $ map (ResultModuleId . moduleId) $ newest as $ selectModules (filters . moduleId) dbval
+	-- list packages
+	listPackages' _ copts = do
+		dbval <- getDb copts
+		return $ ResultOk $ ResultList $
+			map ResultPackage $ nub $ sort $
+			mapMaybe (moduleCabalPackage . moduleLocation) $
+			allModules dbval
+	-- list projects
+	listProjects' _ copts = do
+		dbval <- getDb copts
+		return $ ResultOk $ ResultList $ map ResultProject $ M.elems $ databaseProjects dbval
+	-- get symbol info
+	symbol' as ns copts = errorT $ do
+		dbval <- liftM (localsDatabase as) $ liftIO $ getDb copts
+		proj <- askProject copts as
+		file <- traverse (canonicalizePath' copts) $ askOpt "file" as
+		cabal <- askCabal copts as
+		let
+			filters = checkModule $ allOf $ catMaybes [
+				fmap inProject proj,
+				fmap inFile file,
+				fmap inModule (askOpt "module" as),
+				fmap inPackage (askOpt "package" as),
+				fmap inVersion (askOpt "version" as),
+				fmap inCabal cabal,
+				if hasOpt "src" as then Just byFile else Nothing,
+				if hasOpt "stand" as then Just standalone else Nothing]
+			toResult = ResultList . map ResultModuleDeclaration . newest as . filterMatch as . filter filters
+		case ns of
+			[] -> return $ toResult $ allDeclarations dbval
+			[nm] -> liftM toResult (findDeclaration dbval nm) `catchError` (\e ->
+				throwError ("Can't find symbol: " ++ e))
+			_ -> throwError "Too much arguments"
+	-- get module info
+	modul' as _ copts = errorT' $ do
+		dbval <- liftM (localsDatabase as) $ liftIO $ getDb copts
+		proj <- mapErrorT (fmap $ strMsg +++ id) $ askProject copts as
+		cabal <- mapErrorT (fmap $ strMsg +++ id) $ askCabal copts as
+		file' <- traverse (canonicalizePath' copts) $ askOpt "file" as
+		let
+			filters = allOf $ catMaybes [
+				fmap inProject proj,
+				fmap inCabal cabal,
+				fmap inFile file',
+				fmap inModule (askOpt "module" as),
+				fmap inPackage (askOpt "package" as),
+				fmap inVersion (askOpt "version" as),
+				if hasOpt "src" as then Just byFile else Nothing]
+		rs <- mapErrorT (fmap $ strMsg +++ id) $
+			(newest as . filter (filters . moduleId)) <$> maybe
+				(return $ allModules dbval)
+				(findModule dbval)
+				(askOpt "module" as)
+		case rs of
+			[] -> throwError $ err "Module not found"
+			[m] -> return $ ResultModule m
+			ms' -> throwError $ errArgs "Ambiguous modules" [("modules", ResultList $ map (ResultModuleId . moduleId) ms')]
+	-- get project info
+	project' as _ copts = errorT $ do
+		proj <- askProject copts as
+		proj' <- maybe (throwError "Specify project name of .cabal file") return proj
+		return $ ResultProject proj'
+	-- lookup info about symbol
+	lookup' as [nm] copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		(srcFile, cabal) <- askCtx copts as
+		liftM (ResultList . map ResultModuleDeclaration) $ lookupSymbol dbval cabal srcFile nm
+	lookup' as _ copts = return $ err "Invalid arguments"
+	-- get detailed info about symbol in source file
+	whois' as [nm] copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		(srcFile, cabal) <- askCtx copts as
+		liftM (ResultList . map ResultModuleDeclaration) $ whois dbval cabal srcFile nm
+	whois' as _ copts = return $ err "Invalid arguments"
+	-- get modules accessible from module
+	scopeModules' as [] copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		(srcFile, cabal) <- askCtx copts as
+		liftM (ResultList . map (ResultModuleId . moduleId)) $ scopeModules dbval cabal srcFile
+	scopeModules' as _ copts = return $ err "Invalid arguments"
+	-- get declarations accessible from module
+	scope' as [] copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		(srcFile, cabal) <- askCtx copts as
+		liftM (ResultList . map ResultModuleDeclaration . filterMatch as) $ scope dbval cabal srcFile (hasOpt "global" as)
+	scope' as _ copts = return $ err "Invalid arguments"
+	-- completion
+	complete' as [] copts = complete' as [""] copts
+	complete' as [input] copts = errorT $ do
+		dbval <- getDb copts
+		(srcFile, cabal) <- askCtx copts as
+		liftM (ResultList . map ResultModuleDeclaration) $ completions dbval cabal srcFile input
+	complete' as _ copts = return $ err "Invalid arguments"
+	-- hayoo
+	hayoo' as [] copts = return $ err "Query not specified"
+	hayoo' as [query] copts = errorT $
+		liftM
+			(ResultList . map (ResultModuleDeclaration . Hayoo.hayooAsDeclaration) . Hayoo.hayooFunctions) $
+			Hayoo.hayoo query
+	hayoo' as _ copts = return $ err "Too much arguments"
+	-- cabal list
+	cabalList' as qs copts = errorT $ do
+		ps <- Cabal.cabalList qs
+		return $ ResultList $ map (ResultJSON . toJSON) ps
+	-- ghc-mod type
+	ghcmodType' as [line] copts = ghcmodType' as [line, "1"] copts
+	ghcmodType' as [line, column] copts = errorT $ do
+		line' <- maybe (throwError "line must be a number") return $ readMaybe line
+		column' <- maybe (throwError "column must be a number") return $ readMaybe column
+		dbval <- liftIO $ getDb copts
+		(srcFile, cabal) <- askCtx copts as
+		(srcFile', m, mproj) <- fileCtx dbval srcFile
+		tr <- GhcMod.typeOf (getGhcOpts as) cabal srcFile' mproj (moduleName m) line' column'
+		return $ ResultList $ map ResultTyped tr
+	ghcmodType' as [] copts = return $ err "Specify line"
+	ghcmodType' as _ copts = return $ err "Too much arguments"
+	-- dump cabal modules
+	dumpCabal' as _ copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		cabal <- getCabal copts as
+		let
+			dat = cabalDB cabal dbval
+		liftM (fromMaybe (ResultDatabase dat)) $ runMaybeT $ msum [
+			maybeOpt "path" as $ canonicalizePath' copts >=> \p ->
+				fork (dump (p </> cabalCache cabal) dat),
+			maybeOpt "file" as $ canonicalizePath' copts >=> \f ->
+				fork (dump f dat)]
+	-- dump projects
+	dumpProjects' as [] copts = errorT $ do
+		dbval <- liftIO $ getDb copts
+		ps' <- traverse (getProject copts) $ askOpts "project" as
+		let
+			ps = if null ps' then M.elems (databaseProjects dbval) else ps'
+			dats = map (id &&& flip projectDB dbval) ps
+		liftM (fromMaybe (ResultList $ map (ResultDatabase . snd) dats)) $
+			runMaybeT $ msum [
+				maybeOpt "path" as $ canonicalizePath' copts >=> \p ->
+					fork (forM_ dats $ \(proj, dat) -> (dump (p </> projectCache proj) dat)),
+				maybeOpt "file" as $ canonicalizePath' copts >=> \f ->
+					fork (dump f (mconcat $ map snd dats))]
+	dumpProjects' as _ copts = return $ err "Invalid arguments"
+	-- dump files
+	dumpFiles' as [] copts = do
+		dbval <- getDb copts
+		let
+			dat = standaloneDB dbval
+		liftM (ResultOk . fromMaybe (ResultDatabase dat)) $ runMaybeT $ msum [
+			maybeOpt "path" as $ canonicalizePath' copts >=> \p ->
+				fork (dump (p </> standaloneCache) dat),
+			maybeOpt "file" as $ canonicalizePath' copts >=> \f ->
+				fork (dump f dat)]
+	dumpFiles' as _ copts = return $ err "Invalid arguments"
+	-- dump database
+	dump' as _ copts = do
+		dbval <- getDb copts
+		liftM (fromMaybe (ResultOk $ ResultDatabase dbval)) $ runMaybeT $ msum [
+			do
+				p <- MaybeT $ traverse (canonicalizePath' copts) $ askOpt "path" as
+				fork $ SC.dump p $ structurize dbval
+				return ok,
+			do
+				f <- MaybeT $ traverse (canonicalizePath' copts) $ askOpt "file" as
+				fork $ dump f dbval
+				return ok]
+	-- load database
+	load' as _ copts = do
+		res <- liftM (fromMaybe (err "Specify one of: --path, --file or --data")) $ runMaybeT $ msum [
+			do
+				p <- MaybeT $ return $ askOpt "path" as
+				forkOrWait as $ cacheLoad copts (liftA merge <$> SC.load p)
+				return ok,
+			do
+				f <- MaybeT $ return $ askOpt "file" as
+				e <- liftIO $ doesFileExist f
+				forkOrWait as $ when e $ cacheLoad copts (load f)
+				return ok,
+			do
+				dat <- MaybeT $ return $ askOpt "data" as
+				forkOrWait as $ cacheLoad copts (return $ eitherDecode (toUtf8 dat))
+				return ok]
+		waitDb copts as
+		return res
+	-- link to server
+	link' as _ copts = do
+		race [void (commandWaitInput copts) `finally` commandExit copts, commandLink copts]
+		return ok
+	-- exit
+	exit' _ copts = do
+		commandExit copts
+		return ok
+
+	-- Helper functions
+	cmd' :: [String] -> [String] -> String -> [OptDescr Opts] -> (Opts -> [String] -> a) -> Command (WithOpts a)
+	cmd' name posArgs descr as act = cmd name posArgs descr as act' where
+		act' os args = WithOpts (act os args) $
+			return $ name ++ args ++ optsToArgs os
+
+	cmd_' :: [String] -> [String] -> String -> ([String] -> a) -> Command (WithOpts a)
+	cmd_' name posArgs descr act = cmd_ name posArgs descr act' where
+		act' args = WithOpts (act args) $
+			return $ name ++ args ++ optsToArgs defaultConfig
+
+	getGhcOpts = askOpts "ghc"
+
+	toPair :: Module -> Maybe (FilePath, Module)
+	toPair m = case moduleLocation m of
+		FileModule f _ -> Just (f, m)
+		_ -> Nothing
+
+	modCabal :: Module -> Maybe Cabal
+	modCabal m = case moduleLocation m of
+		CabalModule c _ _ -> Just c
+		_ -> Nothing
+
+	waitDb copts as = when (hasOpt "wait" as) $ do
+		commandLog copts "wait for db"
+		DB.wait (dbVar copts)
+		commandLog copts "db done"
+
+	forkOrWait as act
+		| hasOpt "wait" as = liftIO act
+		| otherwise = liftIO $ void $ forkIO act
+
+	cacheLoad copts act = do
+		db' <- act
+		case db' of
+			Left e -> commandLog copts e
+			Right database -> DB.update (dbVar copts) (return database)
+
+	asCabal :: CommandOptions -> Maybe FilePath -> ErrorT String IO Cabal
+	asCabal copts = maybe
+		(return Cabal)
+		(canonicalizePath' copts >=> locateSandbox)
+
+	askCabal :: CommandOptions -> Opts -> ErrorT String IO (Maybe Cabal)
+	askCabal copts as = traverse (asCabal copts) $ askOptDef "sandbox" as
+
+	getCabal :: CommandOptions -> Opts -> ErrorT String IO Cabal
+	getCabal copts as = asCabal copts $ askOpt "sandbox" as
+
+	getProject :: CommandOptions -> String -> ErrorT String IO Project
+	getProject copts proj = do
+		db' <- getDb copts
+		proj' <- liftM addCabal $ canonicalizePath' copts proj
+		let
+			result =
+				M.lookup proj' (databaseProjects db') <|>
+				find ((== proj) . projectName) (M.elems $ databaseProjects db')
+		maybe (throwError $ "Project " ++ proj ++ " not found") return result
+		where
+			addCabal p
+				| takeExtension p == ".cabal" = p
+				| otherwise = p </> (takeBaseName p <.> "cabal")
+
+	localsDatabase :: Opts -> Database -> Database
+	localsDatabase as
+		| hasOpt "locals" as = databaseLocals
+		| otherwise = id
+
+	newest :: Symbol a => Opts -> [a] -> [a]
+	newest as
+		| hasOpt "no-last" as = id
+		| otherwise = newestPackage
+
+	askProject :: CommandOptions -> Opts -> ErrorT String IO (Maybe Project)
+	askProject copts = traverse (getProject copts) . askOpt "project"
+
+	askFile :: CommandOptions -> Opts -> ErrorT String IO (Maybe FilePath)
+	askFile copts = traverse (canonicalizePath' copts) . askOpt "file"
+
+	forceJust :: String -> ErrorT String IO (Maybe a) -> ErrorT String IO a
+	forceJust msg act = act >>= maybe (throwError msg) return
+
+	askCtx :: CommandOptions -> Opts -> ErrorT String IO (FilePath, Cabal)
+	askCtx copts as = liftM2 (,)
+		(forceJust "No file specified" $ askFile copts as)
+		(getCabal copts as)
+
+	getDb :: (MonadIO m) => CommandOptions -> m Database
+	getDb = liftIO . DB.readAsync . commandDatabase
+
+	dbVar :: CommandOptions -> DB.Async Database
+	dbVar = commandDatabase
+
+	canonicalizePath' :: MonadIO m => CommandOptions -> FilePath -> m FilePath
+	canonicalizePath' copts f = liftIO $ canonicalizePath (normalise f') where
+		f'
+			| isRelative f = commandRoot copts </> f
+			| otherwise = f
+
+	startProcess :: Opts -> ((Value -> IO ()) -> IO ()) -> IO CommandResult
+	startProcess as f
+		| hasOpt "wait" as = return $ ResultProcess (f . onMsg)
+		| otherwise = forkIO (f $ const $ return ()) >> return ok
+		where
+			onMsg showMsg
+				| hasOpt "status" as = showMsg
+				| otherwise = const $ return ()
+
+	error_ :: ErrorT String IO CommandResult -> IO CommandResult
+	error_ = liftM (either err id) . runErrorT
+
+	errorT :: ErrorT String IO ResultValue -> IO CommandResult
+	errorT = liftM (either err ResultOk) . runErrorT
+
+	errorT' :: ErrorT CommandResult IO ResultValue -> IO CommandResult
+	errorT' = liftM (either id ResultOk) . runErrorT
+
+	updateProcess :: CommandOptions -> Opts -> ErrorT String (Update.UpdateDB IO) () -> IO CommandResult
+	updateProcess opts as act = startProcess as $ \onStatus -> Update.updateDB (Update.Settings (commandDatabase opts) (commandReadCache opts) onStatus (getGhcOpts as)) act
+
+	fork :: MonadIO m => IO () -> m ()
+	fork = voidm . liftIO . forkIO
+
+	voidm :: Monad m => m a -> m ()
+	voidm act = act >> return ()
+
+	maybeOpt :: Monad m => String -> Opts -> (String -> MaybeT m a) -> MaybeT m ResultValue
+	maybeOpt n as act = do
+		p <- MaybeT $ return $ askOpt n as
+		act p
+		return ResultNone
+
+	filterMatch :: Opts -> [ModuleDeclaration] -> [ModuleDeclaration]
+	filterMatch as = findMatch as . prefMatch as
+
+	findMatch :: Opts -> [ModuleDeclaration] -> [ModuleDeclaration]
+	findMatch as = case askOpt "find" as of
+		Nothing -> id
+		Just str -> filter (match' str)
+		where
+			match' str m = str `isInfixOf` declarationName (moduleDeclaration m)
+
+	prefMatch :: Opts -> [ModuleDeclaration] -> [ModuleDeclaration]
+	prefMatch as = case fmap splitIdentifier (askOpt "prefix" as) of
+		Nothing -> id
+		Just (qname, pref) -> filter (match' qname pref)
+		where
+			match' qname pref m =
+				pref `isPrefixOf` declarationName (moduleDeclaration m) &&
+				maybe True (== moduleIdName (declarationModuleId m)) qname
+
+processCmd :: CommandOptions -> Int -> String -> (Response -> IO ()) -> IO ()
+processCmd copts tm cmdLine sendResponse = processCmdArgs copts tm (splitArgs cmdLine) sendResponse
+
+-- | Process command, returns 'False' if exit requested
+processCmdArgs :: CommandOptions -> Int -> [String] -> (Response -> IO ()) -> IO ()
+processCmdArgs copts tm cmdArgs sendResponse = run (map (fmap withOptsAct) commands) (asCmd unknownCommand) (asCmd . commandError) cmdArgs tm copts >>= sendResponses where
+	asCmd :: CommandResult -> (Int -> CommandOptions -> IO CommandResult)
+	asCmd r _ _ = return r
+
+	unknownCommand :: CommandResult
+	unknownCommand = err "Unknown command"
+	commandError :: [String] -> CommandResult
+	commandError errs = errArgs "Command syntax error" [("what", ResultList $ map ResultString errs)]
+
+	sendResponses :: CommandResult -> IO ()
+	sendResponses (ResultOk v) = sendResponse $ Response $ toJSON v
+	sendResponses (ResultError e args) = sendResponse $ Response $ object [
+		"error" .= e,
+		"details" .= args]
+	sendResponses (ResultProcess act) = do
+		act (sendResponse . ResponseStatus)
+		sendResponses ok
+		`catch`
+		processFailed
+		where
+			processFailed :: SomeException -> IO ()
+			processFailed e = sendResponses $ errArgs "process throws exception" [
+				("exception", ResultString $ show e)]
+
+hGetLine' :: Handle -> IO ByteString
+hGetLine' = fmap L.fromStrict . B.hGetLine
+
+race :: [IO ()] -> IO ()
+race acts = do
+	v <- newEmptyMVar
+	forM_ acts $ \a -> forkIO ((a `finally` putMVar v ()) `catch` ignoreError)
+	takeMVar v
+	where
+		ignoreError :: SomeException -> IO ()
+		ignoreError _ = return ()
+ tools/Control/Concurrent/FiniteChan.hs view
@@ -0,0 +1,38 @@+module Control.Concurrent.FiniteChan (
+	Chan,
+	newChan, dupChan, putChan, getChan, readChan, closeChan, stopChan
+	) where
+
+import qualified Control.Concurrent.Chan as C
+import Data.Maybe
+
+-- | 'Chan' is stoppable channel unline 'Control.Concurrent.Chan'
+newtype Chan a = Chan (C.Chan (Maybe a))
+
+-- | Create channel
+newChan :: IO (Chan a)
+newChan = fmap Chan C.newChan
+
+-- | Duplicate channel
+dupChan :: Chan a -> IO (Chan a)
+dupChan (Chan ch) = fmap Chan $ C.dupChan ch
+
+-- | Write data to channel
+putChan :: Chan a -> a -> IO ()
+putChan (Chan ch) = C.writeChan ch . Just
+
+-- | Get data from channel
+getChan :: Chan a -> IO (Maybe a)
+getChan (Chan ch) = C.readChan ch
+
+-- | Read channel contents
+readChan :: Chan a -> IO [a]
+readChan (Chan ch) = fmap (catMaybes . takeWhile isJust) $ C.getChanContents ch
+
+-- | Close channel. 'putChan' will still work, but no data will be available on other ending
+closeChan :: Chan a -> IO ()
+closeChan (Chan ch) = C.writeChan ch Nothing
+
+-- | Stop channel and return all data
+stopChan :: Chan a -> IO [a]
+stopChan ch = closeChan ch >> readChan ch
+ tools/System/Command.hs view
@@ -0,0 +1,209 @@+{-# LANGUAGE GeneralizedNewtypeDeriving, OverloadedStrings, DefaultSignatures, FlexibleInstances #-}
+
+module System.Command (
+	Command(..),
+	DefaultConfig(..),
+	cmd, cmd_,
+	Help(..), addHelpCommand, addHelp,
+	brief, help,
+	run, runCmd,
+	Opts(..),
+	mapOpts, traverseOpts,
+	option, option_, req, noreq, flag,
+	opt, optMaybe, hasOpt, askOpt, askOptDef, askOpts,
+	optsToArgs,
+	splitArgs, unsplitArgs
+	) where
+
+import Control.Arrow
+import Control.Applicative
+import Control.Monad (join)
+import Data.Char
+import Data.List (stripPrefix, unfoldr, isPrefixOf)
+import Data.Maybe (fromMaybe, mapMaybe, listToMaybe, maybeToList)
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Monoid
+import Data.Traversable (traverse)
+import System.Console.GetOpt
+
+-- | Command
+data Command a = Command {
+	commandName :: [String],
+	commandPosArgs :: [String],
+	commandDesc :: Maybe String,
+	commandUsage :: [String],
+	commandRun :: [String] -> Maybe (Either [String] a) }
+
+instance Functor Command where
+	fmap f cmd' = cmd' {
+		commandRun = fmap (fmap f) . commandRun cmd' }
+
+-- | Default value for options
+class DefaultConfig a where
+	defaultConfig :: a
+	default defaultConfig :: Monoid a => a
+	defaultConfig = mempty
+
+instance DefaultConfig ()
+instance DefaultConfig [String]
+
+-- | Make command
+-- >cmd name args desc args' onCmd
+cmd :: (Monoid c, DefaultConfig c) => [String] -> [String] -> String -> [OptDescr c] -> (c -> [String] -> a) -> Command a
+cmd name posArgs descr as act = Command {
+	commandName = name,
+	commandPosArgs = posArgs,
+	commandDesc = descr',
+	commandUsage = lines $ usageInfo (unwords (name ++ map (\a -> "[" ++ a ++ "]") posArgs) ++ maybe "" (" -- " ++) descr') as,
+	commandRun = \opts -> case getOpt Permute as opts of
+		(opts', cs, errs) -> fmap (\cs' -> if null errs then return (act (mconcat opts' `mappend` defaultConfig) cs') else Left errs) (stripPrefix name cs) }
+	where
+		descr' = if null descr then Nothing else Just descr
+
+-- | Make command without params
+cmd_ :: [String] -> [String] -> String -> ([String] -> a) -> Command a
+cmd_ name posArgs descr act = cmd name posArgs descr [] (act' act) where
+	act' :: a -> () -> a
+	act' = const
+
+data Help =
+	HelpUsage [String] |
+	HelpCommands [([String], [String])]
+		deriving (Eq, Ord, Read, Show)
+
+-- | Add help command
+addHelpCommand :: String -> (Either String Help -> a) -> [Command a] -> [Command a]
+addHelpCommand tool toCmd cmds = cmds' where
+	cmds' = helpcmd' : cmds
+	helpcmd = fmap toCmd $ cmd_ ["help"] ["command"] ("help command, also can be called in form '" ++ tool ++ " [command] -?'") onHelp
+	-- allow help by last argument '-?'
+	helpcmd' = helpcmd { commandRun = commandRun helpcmd . rewrite } where
+		rewrite as
+			| last as == "-?" = "help" : init as
+			| otherwise = as
+	onHelp [] = Right $ HelpUsage [tool ++ " " ++ brief c | c <- cmds']
+	onHelp cmdname = case filter ((cmdname `isPrefixOf`) . commandName) cmds' of
+		[] -> Left $ "Unknown command: " ++ unwords cmdname
+		helps -> Right $ HelpCommands $ map (commandName &&& (addHeader . help)) helps
+	addHeader [] = []
+	addHeader (h:hs) = (tool ++ " " ++ h) : hs
+
+-- | Add help commands, which outputs help to stdout
+addHelp :: String -> (IO () -> a) -> [Command a] -> [Command a]
+addHelp tool liftPrint cmds = addHelpCommand tool toCmd cmds where
+	toCmd = liftPrint . either putStrLn printHelp
+	printHelp :: Help -> IO ()
+	printHelp (HelpUsage u) = mapM_ putStrLn $ map ('\t':) u
+	printHelp (HelpCommands cs) = mapM_ putStrLn $ map ('\t':) $ concatMap snd cs
+
+-- | Show brief help for command
+brief :: Command a -> String
+brief = head . commandUsage
+
+-- | Show detailed help for command
+help :: Command a -> [String]
+help = commandUsage
+
+-- | Run commands
+run :: [Command a] -> a -> ([String] -> a) -> [String] -> a
+run cmds onDef onError as = maybe onDef (either onError id) found where
+	found = listToMaybe $ mapMaybe (`commandRun` as) cmds
+
+-- | Try run command, wrapping any negative result to 'Maybe'
+runCmd :: Command a -> [String] -> Maybe a
+runCmd cmd = join . fmap toMaybe . commandRun cmd where
+	toMaybe :: Either b c -> Maybe c
+	toMaybe = either (const Nothing) Just
+
+-- | Options holder
+newtype Opts = Opts { getOpts :: Map String [String] }
+
+instance Monoid Opts where
+	mempty = Opts mempty
+	(Opts l) `mappend` (Opts r) = Opts $ M.unionWith (++) l r
+
+instance DefaultConfig Opts
+
+-- | Map 'Opts'
+mapOpts :: (String -> String) -> Opts -> Opts
+mapOpts f = Opts . M.mapKeys f . getOpts
+
+-- | Traverse 'Opts'
+traverseOpts :: Applicative f => (String -> String -> f String) -> Opts -> f Opts
+traverseOpts f = fmap Opts . M.traverseWithKey (traverse . f) . getOpts
+
+option :: [Char] -> String -> [String] -> (String -> ArgDescr Opts) -> String -> OptDescr Opts
+option fs name names onOption d = Option fs (name:names) (onOption name) d
+
+option_ :: [Char] -> String -> (String -> ArgDescr Opts) -> String -> OptDescr Opts
+option_ fs name = option fs name []
+
+-- | Required option
+req :: String -> String -> ArgDescr Opts
+req nm n = ReqArg (opt n) nm
+
+-- | Optional option
+noreq :: String -> String -> ArgDescr Opts
+noreq nm n = OptArg (optMaybe n) nm
+
+-- | Flag option
+flag :: String -> ArgDescr Opts
+flag n = NoArg flag' where
+	flag' :: Opts
+	flag' = Opts $ M.singleton n []
+
+opt :: String -> String -> Opts
+opt n v = Opts $ M.singleton n [v]
+
+optMaybe :: String -> Maybe String -> Opts
+optMaybe n = Opts . M.singleton n . maybeToList
+
+hasOpt :: String -> Opts -> Bool
+hasOpt n = M.member n . getOpts
+
+askOpt :: String -> Opts -> Maybe String
+askOpt n = listToMaybe . askOpts n
+
+askOptDef :: String -> Opts -> Maybe (Maybe String)
+askOptDef n = fmap listToMaybe . M.lookup n . getOpts
+
+askOpts :: String -> Opts -> [String]
+askOpts n = fromMaybe [] . M.lookup n . getOpts
+
+-- | Print 'Opts' as args
+optsToArgs :: Opts -> [String]
+optsToArgs = concatMap optToArgs . M.toList . getOpts where
+	optToArgs :: (String, [String]) -> [String]
+	optToArgs (n, []) = ["--" ++ n]
+	optToArgs (n, vs) = ["--"++ n ++ "=" ++ v | v <- vs]
+
+-- | Split string to words
+splitArgs :: String -> [String]
+splitArgs "" = []
+splitArgs (c:cs)
+	| isSpace c = splitArgs cs
+	| c == '"' = let (w, cs') = readQuote cs in w : splitArgs cs'
+	| otherwise = let (ws, tl) = break isSpace cs in (c:ws) : splitArgs tl
+	where
+		readQuote :: String -> (String, String)
+		readQuote "" = ("", "")
+		readQuote ('\\':ss)
+			| null ss = ("\\", "")
+			| otherwise = first (head ss :) $ readQuote (tail ss)
+		readQuote ('"':ss) = ("", ss)
+		readQuote (s:ss) = first (s:) $ readQuote ss
+
+unsplitArgs :: [String] -> String
+unsplitArgs = unwords . map escape where
+	escape :: String -> String
+	escape str
+		| any isSpace str || '"' `elem` str = "\"" ++ concat (unfoldr escape' str) ++ "\""
+		| otherwise = str
+	escape' :: String -> Maybe (String, String)
+	escape' [] = Nothing
+	escape' (ch:tl) = Just (escaped, tl) where
+		escaped = case ch of
+			'"' -> "\\\""
+			'\\' -> "\\\\"
+			_ -> [ch]
+ tools/System/Win32/FileMapping/Memory.hs view
@@ -0,0 +1,58 @@+module System.Win32.FileMapping.Memory (
+	createMap, openMap, mapFile,
+	withMapFile, readMapFile
+	) where
+
+import Control.Monad.CatchIO (bracket)
+import Control.Monad.Cont
+import Control.Monad.Error
+import Data.ByteString.Char8
+import qualified Data.ByteString.Char8 as BS
+import Foreign.C.String
+import Foreign.Ptr
+import System.Win32.File (closeHandle)
+import System.Win32.FileMapping hiding (mapFile)
+import System.Win32.Types
+import System.Win32.Mem
+
+createMap :: Maybe HANDLE -> ProtectFlags -> DDWORD -> Maybe String -> ContT r (ErrorT String IO) HANDLE
+createMap mh pf sz mn = ContT $ bracket
+	(verify iNVALID_HANDLE_VALUE "Invalid handle" $
+		liftIO (createFileMapping mh pf sz mn))
+	(liftIO . closeHandle)
+
+openMap :: FileMapAccess -> Bool -> Maybe String -> ContT r (ErrorT String IO) HANDLE
+openMap f i mn = ContT $ bracket
+	(verify iNVALID_HANDLE_VALUE "Invalid handle" $
+		liftIO (openFileMapping f i mn))
+	(liftIO . 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) 
+
+-- | 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))
+	h <- createMap Nothing pAGE_READWRITE (fromIntegral len) (Just name)
+	ptr <- lift $ mapFile h fILE_MAP_ALL_ACCESS 0 0
+	liftIO $ do
+		copyMemory ptr p (fromIntegral len)
+		unmapViewOfFile ptr
+		act
+	where
+		len = BS.length str + 1
+
+-- | Read data from named map view of file
+readMapFile :: String -> ErrorT String IO ByteString
+readMapFile name = 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 v str act = do
+	x <- act
+	if x == v
+		then throwError (strMsg str)
+		else return x
+ tools/System/Win32/FileMapping/NamePool.hs view
@@ -0,0 +1,31 @@+module System.Win32.FileMapping.NamePool (
+	Pool(..),
+	createPool, withName
+	) where
+
+import Control.Concurrent
+import Control.Exception (bracket)
+import Control.Monad (liftM, liftM2)
+import System.Win32.FileMapping.Memory ()
+
+-- | Pool of names for memory mapped files
+data Pool = Pool {
+	poolFreeNames :: MVar [String],
+	poolNewName :: IO String }
+
+-- | Create pool of numbered names by base name
+createPool :: String -> IO Pool
+createPool baseName = liftM2 Pool (newMVar []) mkNewName where
+	mkNewName :: IO (IO String)
+	mkNewName = do
+		num <- newMVar 0
+		return $ modifyMVar num $ \n -> do
+			return (succ n, baseName ++ show n)
+
+-- | Use free name from pool
+withName :: Pool -> (String -> IO a) -> IO a
+withName p act = bracket getName freeName act where
+	getName = modifyMVar (poolFreeNames p) $ \names -> case names of
+		[] -> liftM ((,) []) $ poolNewName p
+		(n:ns) -> return (ns, n)
+	freeName name = modifyMVar_ (poolFreeNames p) (return . (name:))
+ tools/Tool.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module Tool (
+	-- * Tool
+	toolMain, usage,
+	-- * Json command
+	jsonCmd, jsonCmd_,
+	-- * Errors
+	toolError,
+	-- * Options
+	prettyOpt, isPretty,
+
+	module System.Command
+	) where
+
+import Control.Monad.Error (ErrorT, runErrorT, throwError)
+import Data.Aeson
+import Data.Aeson.Encode.Pretty (encodePretty)
+import qualified Data.ByteString.Lazy.Char8 as L (ByteString, putStrLn)
+import System.Console.GetOpt
+import System.Environment
+import System.IO
+
+import HsDev.Tools.Base (ToolM)
+
+import System.Command
+
+-- | Run tool with commands
+toolMain :: String -> [Command (IO ())] -> IO ()
+toolMain name commands = do
+	hSetBuffering stdout LineBuffering
+	hSetEncoding stdout utf8
+	as <- getArgs
+	case as of
+		[] -> usage name toolCmds
+		_ -> run toolCmds unknownCmd onError as
+	where
+		onError :: [String] -> IO ()
+		onError = mapM_ putStrLn
+
+		unknownCmd :: IO ()
+		unknownCmd = putStrLn "Unknown command" >> usage name toolCmds
+
+		toolCmds = addHelp name id commands
+
+-- | Print usage
+usage :: String -> [Command (IO ())] -> IO ()
+usage toolName = mapM_ (putStrLn . ('\t':) . ((toolName ++ " ") ++) . brief)
+
+-- | Command with JSONable result
+jsonCmd :: ToJSON a => [String] -> [String] -> String -> [OptDescr Opts] -> (Opts -> [String] -> ToolM a) -> Command (IO ())
+jsonCmd name posArgs descr as act = cmd name posArgs descr (prettyOpt : as) $ \opts as -> do
+	r <- runErrorT $ act opts as
+	L.putStrLn $ either (toStr opts . errorStr) (toStr opts) r
+	where
+		toStr :: ToJSON a => Opts -> a -> L.ByteString
+		toStr opts
+			| isPretty opts = encodePretty
+			| otherwise = encode
+
+		errorStr :: String -> Value
+		errorStr s = object ["error" .= s]
+
+-- | `jsonCmd` with the only '--pretty' option
+jsonCmd_ :: ToJSON a => [String] -> [String] -> String -> ([String] -> ToolM a) -> Command (IO ())
+jsonCmd_ name posArgs descr = jsonCmd name posArgs descr [] . const
+
+-- | Fail with error
+toolError :: String -> ToolM a
+toolError = throwError
+
+prettyOpt :: OptDescr Opts
+prettyOpt = option_ [] "pretty" flag "pretty JSON output"
+
+isPretty :: Opts -> Bool
+isPretty = hasOpt "pretty"
+ tools/Types.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module Types (
+	-- * Server options
+	ServerOpts(..), serverOpts, serverOptsToArgs,
+	-- * Client options
+	ClientOpts(..), clientOpts,
+	-- * Messages and results
+	ResultValue(..), Response(..),
+	CommandResult(..), ok, err, errArgs, details,
+	WithOpts(..),
+	CommandOptions(..), CommandAction
+	) where
+
+import Control.Applicative
+import Control.Monad.Error
+import Data.Aeson
+import Data.Map (Map)
+import Data.Monoid
+import qualified Data.HashMap.Strict as HM
+import qualified Data.Map as M
+import System.Console.GetOpt
+import Text.Read
+
+import HsDev.Database
+import HsDev.Project
+import HsDev.Symbols
+import HsDev.Tools.GhcMod (TypedRegion)
+import qualified HsDev.Database.Async as DB
+
+import System.Command
+
+-- | Server options
+data ServerOpts = ServerOpts {
+	serverPort :: First Int,
+	serverTimeout :: First Int,
+	serverLog :: First String,
+	serverCache :: First FilePath,
+	serverLoadCache :: Any }
+
+instance DefaultConfig ServerOpts where
+	defaultConfig = ServerOpts
+		(First $ Just 4567)
+		(First $ Just 1000)
+		(First Nothing)
+		(First Nothing)
+		mempty
+
+instance Monoid ServerOpts where
+	mempty = ServerOpts mempty mempty mempty mempty mempty
+	l `mappend` r = ServerOpts
+		(serverPort l `mappend` serverPort r)
+		(serverTimeout l `mappend` serverTimeout r)
+		(serverLog l `mappend` serverLog r)
+		(serverCache l `mappend` serverCache r)
+		(serverLoadCache l `mappend` serverLoadCache r)
+
+-- | Server options command opts
+serverOpts :: [OptDescr ServerOpts]
+serverOpts = [
+	Option [] ["port"] (ReqArg (\p -> mempty { serverPort = First (readMaybe p) }) "number") "listen port",
+	Option [] ["timeout"] (ReqArg (\t -> mempty { serverTimeout = First (readMaybe t) }) "msec") "query timeout",
+	Option ['l'] ["log"] (ReqArg (\l -> mempty { serverLog = First (Just l) }) "file") "log file",
+	Option [] ["cache"] (ReqArg (\p -> mempty { serverCache = First (Just p) }) "path") "cache directory",
+	Option [] ["load"] (NoArg (mempty { serverLoadCache = Any True })) "force load all data from cache on startup"]
+
+-- | Convert 'ServerOpts' to args
+serverOptsToArgs :: ServerOpts -> [String]
+serverOptsToArgs sopts = concat [
+	arg' "port" show $ serverPort sopts,
+	arg' "timeout" show $ serverTimeout sopts,
+	arg' "log" id $ serverLog sopts,
+	arg' "cache" id $ serverCache sopts,
+	if getAny (serverLoadCache sopts) then ["--load"] else []]
+	where
+		arg' :: String -> (a -> String) -> First a -> [String]
+		arg' name str = maybe [] (\v -> ["--" ++ name, str v]) . getFirst
+
+-- | Client options
+data ClientOpts = ClientOpts {
+	clientPort :: First Int,
+	clientPretty :: Any,
+	clientData :: Any }
+
+instance DefaultConfig ClientOpts where
+	defaultConfig = ClientOpts (First $ Just 4567) mempty mempty
+
+instance Monoid ClientOpts where
+	mempty = ClientOpts mempty mempty mempty
+	l `mappend` r = ClientOpts
+		(clientPort l `mappend` clientPort r)
+		(clientPretty l `mappend` clientPretty r)
+		(clientData l `mappend` clientData r)
+
+-- | Client options command opts
+clientOpts :: [OptDescr ClientOpts]
+clientOpts = [
+	Option [] ["port"] (ReqArg (\p -> mempty { clientPort = First (readMaybe p) }) "number") "connection port",
+	Option [] ["pretty"] (NoArg (mempty { clientPretty = Any True })) "pretty json output",
+	Option [] ["stdin"] (NoArg (mempty { clientData = Any True })) "pass data to stdin"]
+
+data ResultValue =
+	ResultDatabase Database |
+	ResultDeclaration Declaration |
+	ResultModuleDeclaration ModuleDeclaration |
+	ResultModuleId ModuleId |
+	ResultModule Module |
+	ResultInspectedModule InspectedModule |
+	ResultPackage ModulePackage |
+	ResultProject Project |
+	ResultTyped TypedRegion |
+	ResultList [ResultValue] |
+	ResultMap (Map String ResultValue) |
+	ResultJSON Value |
+	ResultString String |
+	ResultNone
+
+instance ToJSON ResultValue where
+	toJSON (ResultDatabase db) = toJSON db
+	toJSON (ResultDeclaration d) = toJSON d
+	toJSON (ResultModuleDeclaration md) = toJSON md
+	toJSON (ResultModuleId mid) = toJSON mid
+	toJSON (ResultModule m) = toJSON m
+	toJSON (ResultInspectedModule m) = toJSON m
+	toJSON (ResultPackage p) = toJSON p
+	toJSON (ResultProject p) = toJSON p
+	toJSON (ResultTyped t) = toJSON t
+	toJSON (ResultList l) = toJSON l
+	toJSON (ResultMap m) = toJSON m
+	toJSON (ResultJSON v) = toJSON v
+	toJSON (ResultString s) = toJSON s
+	toJSON ResultNone = toJSON $ object []
+
+instance FromJSON ResultValue where
+	parseJSON v = foldr1 (<|>) [
+		do
+			(Object m) <- parseJSON v
+			if HM.null m then return ResultNone else mzero,
+		ResultDatabase <$> parseJSON v,
+		ResultDeclaration <$> parseJSON v,
+		ResultModuleDeclaration <$> parseJSON v,
+		ResultModuleId <$> parseJSON v,
+		ResultModule <$> parseJSON v,
+		ResultInspectedModule <$> parseJSON v,
+		ResultPackage <$> parseJSON v,
+		ResultProject <$> parseJSON v,
+		ResultTyped <$> parseJSON v,
+		ResultList <$> parseJSON v,
+		ResultMap <$> parseJSON v,
+		pure $ ResultJSON v,
+		ResultString <$> parseJSON v]
+
+data Response =
+	Response Value |
+	ResponseStatus Value |
+	ResponseMapFile String
+
+instance ToJSON Response where
+	toJSON (Response v) = object ["result" .= v]
+	toJSON (ResponseStatus v) = object ["status" .= v]
+	toJSON (ResponseMapFile s) = object ["file" .= s]
+
+instance FromJSON Response where
+	parseJSON = withObject "response" (\v ->
+		(Response <$> (v .: "result")) <|>
+		(ResponseStatus <$> (v .: "status")) <|>
+		(ResponseMapFile <$> (v .: "file")))
+
+data CommandResult =
+	ResultOk ResultValue |
+	ResultError String (Map String ResultValue) |
+	ResultProcess ((Value -> IO ()) -> IO ())
+
+instance Error CommandResult where
+	noMsg = ResultError noMsg mempty
+	strMsg s = ResultError s mempty
+
+ok :: CommandResult
+ok = ResultOk ResultNone
+
+err :: String -> CommandResult
+err s = ResultError s M.empty
+
+errArgs :: String -> [(String, ResultValue)] -> CommandResult
+errArgs s as = ResultError s (M.fromList as)
+
+-- | Add detailed information to error message
+details :: [(String, ResultValue)] -> CommandResult -> CommandResult
+details as (ResultError s cs) = ResultError s (M.union (M.fromList as) cs)
+details _ r = r
+
+data WithOpts a = WithOpts {
+	withOptsAct :: a,
+	withOptsArgs :: IO [String] }
+
+instance Functor WithOpts where
+	fmap f (WithOpts x as) = WithOpts (f x) as
+
+data CommandOptions = CommandOptions {
+	commandDatabase :: DB.Async Database,
+	commandWriteCache :: Database -> IO (),
+	commandReadCache :: (FilePath -> ErrorT String IO Structured) -> IO (Maybe Database),
+	commandRoot :: FilePath,
+	commandLog :: String -> IO (),
+	commandWaitInput :: IO String,
+	commandLink :: IO (),
+	commandExit :: IO () }
+
+type CommandAction = WithOpts (Int -> CommandOptions -> IO CommandResult)
+
+ tools/Update.hs view
@@ -0,0 +1,189 @@+{-# LANGUAGE FlexibleContexts, GeneralizedNewtypeDeriving, OverloadedStrings, MultiParamTypeClasses #-}
+
+module Update (
+	Settings(..),
+
+	UpdateDB,
+	updateDB,
+
+	setStatus, waiter, updater, loadCache, runTask, runTasks,
+	status, readDB,
+
+	scanModule, scanModules, scanFile, scanCabal, scanProject, scanDirectory,
+
+	-- * Helpers
+	liftErrorT
+	) where
+
+import Control.Applicative (Applicative(..))
+import Control.Monad.CatchIO
+import Control.Monad.Error
+import Control.Monad.Reader
+import Data.Aeson
+import qualified Data.HashMap.Strict as HM
+import Data.Map (Map)
+import qualified Data.Map as M
+import Data.Maybe (mapMaybe)
+import Data.Monoid
+import Data.Traversable (traverse)
+import System.Directory (canonicalizePath)
+
+import qualified HsDev.Cache.Structured as Cache
+import HsDev.Database
+import HsDev.Database.Async
+import HsDev.Project
+import HsDev.Symbols
+import HsDev.Tools.HDocs
+import qualified HsDev.Scan as S
+import HsDev.Scan.Browse
+
+data Settings = Settings {
+	database :: Async Database,
+	databaseCacheReader :: (FilePath -> ErrorT String IO Structured) -> IO (Maybe Database),
+	onStatus :: Value -> IO (),
+	ghcOptions :: [String] }
+
+newtype UpdateDB m a = UpdateDB { runUpdateDB :: ReaderT Settings m a }
+	deriving (Applicative, Monad, MonadIO, MonadCatchIO, Functor, MonadReader Settings)
+
+-- | Run `UpdateDB` monad
+updateDB :: Monad m => Settings -> ErrorT String (UpdateDB m) () -> m ()
+updateDB sets act = runUpdateDB (runErrorT act >> return ()) `runReaderT` sets
+
+-- | Set partial status
+setStatus :: MonadReader Settings m => Value -> m a -> m a
+setStatus v act = local alter act where
+	alter s = s {
+		onStatus = \st -> onStatus s (v `union` st) }
+
+-- | Wait DB to complete actions
+waiter :: (MonadIO m, MonadReader Settings m) => m () -> m ()
+waiter act = do
+	db <- asks database
+	act
+	wait db
+
+-- | Update task result to database
+updater :: (MonadIO m, MonadReader Settings m) => m Database -> m ()
+updater act = do
+	db <- asks database
+	act >>= update db . return
+
+-- | Load data from cache and wait
+loadCache :: (MonadIO m, MonadReader Settings m) => (FilePath -> ErrorT String IO Structured) -> m ()
+loadCache act = do
+	cacheReader <- asks databaseCacheReader
+	mdat <- liftIO $ cacheReader act
+	case mdat of
+		Nothing -> return ()
+		Just dat -> waiter (updater (return dat))
+
+-- | Run one task
+runTask :: MonadIO m => Value -> ErrorT String (UpdateDB m) a -> ErrorT String (UpdateDB m) a
+runTask v act = object ["task" .= v] `setStatus` wrapAct act where
+	wrapAct :: MonadIO m => ErrorT String (UpdateDB m) a -> ErrorT String (UpdateDB m) a
+	wrapAct a = do
+		status $ object ["status" .= toJSON ("working" :: String)]
+		x <- a
+		status $ object ["status" .= taskOk]
+		return x
+		`catchError`
+		(\e -> status (object ["status" .= taskErr e]) >> throwError e)
+	taskOk = toJSON ("ok" :: String)
+	taskErr e = object ["error" .= e]
+
+-- | Run many tasks with numeration
+runTasks :: Monad m => [ErrorT String (UpdateDB m) ()] -> ErrorT String (UpdateDB m) ()
+runTasks ts = zipWithM_ taskNum [1..] (map noErr ts) where
+	total = length ts
+	taskNum n t = progress `setStatus` t where
+		progress = object ["progress" .= object [
+			"current" .= (n :: Integer), "total" .= total]]
+	noErr v = v `mplus` return ()
+
+-- | Post status
+status :: (MonadIO m, MonadReader Settings m) => Value -> m ()
+status msg = do
+	on' <- asks onStatus
+	liftIO $ on' msg
+
+-- | Get database value
+readDB :: (MonadIO m, MonadReader Settings m) => m Database
+readDB = asks database >>= liftIO . readAsync
+
+-- | Scan module
+scanModule :: MonadCatchIO m => [String] -> ModuleLocation -> ErrorT String (UpdateDB m) ()
+scanModule opts mloc = runTask task' $ do
+	im <- liftErrorT $ S.scanModule opts mloc
+	updater $ return $ fromModule im
+	ErrorT $ return $ inspectionResult im
+	return ()
+	where
+		task' = object ["scanning" .= mloc]
+
+-- | Scan modules
+scanModules :: MonadCatchIO m => [String] -> [S.ModuleToScan] -> ErrorT String (UpdateDB m) ()
+scanModules opts ms = do
+	db <- asks database
+	dbval <- readDB
+	runTask (toJSON ("updating projects files" :: String)) $ updater $ do
+		projects <- mapM (liftErrorT . S.scanProjectFile opts) ps
+		return $ mconcat $ map fromProject projects
+	ms' <- liftErrorT $ filterM (S.changedModule dbval opts . fst) ms
+	runTasks [scanModule (opts ++ snd m) (fst m) | m <- ms']
+	where
+		ps = mapMaybe (toProj . fst) ms
+		toProj (FileModule _ p) = fmap projectCabal p
+		toProj _ = Nothing
+
+-- | Scan source file
+scanFile :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
+scanFile opts fpath = do
+	fpath' <- liftIO $ canonicalizePath fpath
+	mproj <- liftIO $ locateProject fpath'
+	let
+		mtarget = mproj >>= (`fileTarget` fpath')
+		fileExts = maybe [] (extensionsOpts . infoExtensions) mtarget
+
+	scanModule (opts ++ fileExts) (FileModule fpath' mproj)
+
+-- | Scan cabal modules
+scanCabal :: MonadCatchIO m => [String] -> Cabal -> ErrorT String (UpdateDB m) ()
+scanCabal opts sandbox = do
+	loadCache $ Cache.loadCabal sandbox
+	dbval <- readDB
+	ms <- runTask
+		(object ["action" .= ("loading modules" :: String), "sandbox" .= sandbox]) $
+		liftErrorT $ browseFilter opts sandbox (S.changedModule dbval opts)
+	docs <- runTask
+		(object ["action" .= ("loading docs" :: String), "sandbox" .= sandbox]) $
+		liftErrorT $ hdocsCabal sandbox opts
+	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
+
+-- | Scan project
+scanProject :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
+scanProject opts cabal = do
+	proj <- liftErrorT $ S.scanProjectFile opts cabal
+	loadCache $ Cache.loadProject $ projectCabal proj
+	(_, sources) <- liftErrorT $ S.enumProject proj
+	scanModules opts sources
+
+-- | Scan directory for source files and projects
+scanDirectory :: MonadCatchIO m => [String] -> FilePath -> ErrorT String (UpdateDB m) ()
+scanDirectory opts dir = do
+	(projSrcs, standSrcs) <- runTask (toJSON ("getting list of sources" :: String)) $ liftErrorT $ S.enumDirectory dir
+	forM_ projSrcs $ \(p, _) -> loadCache (Cache.loadProject $ projectCabal p)
+	loadCache $ Cache.loadFiles $ mapMaybe (moduleSource . fst) standSrcs
+	scanModules opts (concatMap snd projSrcs ++ standSrcs)
+
+-- | Lift errors
+liftErrorT :: MonadIO m => ErrorT String IO a -> ErrorT String m a
+liftErrorT = mapErrorT liftIO
+
+-- | Merge two JSON object
+union :: Value -> Value -> Value
+union (Object l) (Object r) = Object $ HM.union l r
+union _ _ = error "Commands.union: impossible happened"
+ tools/hscabal.hs view
@@ -0,0 +1,11 @@+module Main (
+	main
+	) where
+
+import HsDev.Tools.Cabal
+
+import Tool
+
+main :: IO ()
+main = toolMain "hscabal" [
+	jsonCmd_ ["list"] ["packages..."] "list hackage packages" cabalList]
+ tools/hsclearimports.hs view
@@ -0,0 +1,52 @@+module Main (
+	main
+	) where
+
+import Control.Exception (finally)
+import Control.Monad (void)
+import Control.Monad.Error
+import System.Console.GetOpt (OptDescr)
+import System.Directory
+import System.Environment (getArgs)
+import Text.Read (readMaybe)
+
+import HsDev.Tools.ClearImports (clearImports)
+import HsDev.Symbols (locateSourceDir)
+
+import System.Command
+
+opts :: [OptDescr Opts]
+opts = [
+	option_ ['g'] "ghc" (req "ghc options") "options for GHC",
+	option_ [] "hide-import-list" flag "hide import list",
+	option_ [] "max-import-list" (req "max import list length") "hide long import lists"]
+
+cmds :: [Command (IO ())]
+cmds = addHelp "hsclearimports" id [
+	cmd [] ["file"] "clear imports in haskell source" opts clear]
+	where
+		clear :: Opts -> [String] -> IO ()
+		clear as [f] = do
+			file <- canonicalizePath f
+			mroot <- locateSourceDir file
+			cur <- getCurrentDirectory
+			flip finally (setCurrentDirectory cur) $ do
+				maybe (return ()) setCurrentDirectory mroot
+				void $ runErrorT $ catchError
+					(clearImports (askOpts "ghc" as) file >>= mapM_ (liftIO . putStrLn . format as))
+					(\e -> liftIO (putStrLn $ "Error: " ++ e))
+		clear _ _ = putStrLn "Invalid arguments"
+		
+		format :: Opts -> (String, String) -> String
+		format as (imp, lst)
+			| hasOpt "hide-import-list" as = imp
+			| maybe False (length lst >) (askOpt "max-import-list" as >>= readMaybe) = imp
+			| otherwise = imp ++ " (" ++ lst ++ ")"
+
+main :: IO ()
+main = do
+	args <- getArgs
+	run cmds noCmd onError args
+	where
+		noCmd = putStrLn "Invalid command"
+		onError = mapM_ putStrLn
+ tools/hsdev.hs view
@@ -0,0 +1,45 @@+module Main (
+	main
+	) where
+
+import Control.Monad
+import Network.Socket
+import System.Environment
+import System.Exit
+import System.IO
+
+import System.Command hiding (brief)
+import qualified System.Command as C (brief)
+
+import Types
+import Commands
+
+main :: IO ()
+main = withSocketsDo $ do
+	hSetBuffering stdout LineBuffering
+	hSetEncoding stdout utf8
+	as <- getArgs
+	when (null as) $ do
+		printMainUsage
+		exitSuccess
+	let
+		asr = if last as == "-?" then "help" : init as else as 
+	run mainCommands onDef onError asr
+	where
+		onError :: [String] -> IO ()
+		onError errs = do
+			mapM_ putStrLn errs
+			exitFailure
+
+		onDef :: IO ()
+		onDef = do
+			putStrLn "Unknown command"
+			exitFailure
+
+printMainUsage :: IO ()
+printMainUsage = do
+	mapM_ (putStrLn . ('\t':) . ("hsdev " ++) . C.brief) mainCommands
+	putStrLn "\thsdev [--port=number] [--pretty] [--stdin] interactive command... -- send command to server, use flag stdin to pass data argument through stdin"
+
+printUsage :: IO ()
+printUsage = mapM_ (putStrLn . ('\t':) . ("hsdev " ++) . C.brief) commands
+ tools/hshayoo.hs view
@@ -0,0 +1,15 @@+module Main (
+	main
+	) where
+
+import Control.Monad (liftM)
+import HsDev.Tools.Hayoo
+
+import Tool
+
+main :: IO ()
+main = toolMain "hshayoo" [
+	jsonCmd_ [] ["query"] "search in hayoo" hayoo']
+	where
+		hayoo' [q] = liftM (map hayooAsDeclaration . hayooFunctions) $ hayoo q
+		hayoo' _ = toolError "Invalid arguments"
+ tools/hsinspect.hs view
@@ -0,0 +1,29 @@+{-# LANGUAGE OverloadedStrings #-}
+
+module Main (
+	main
+	) where
+
+import HsDev.Project (readProject)
+import HsDev.Scan (scanModule)
+import HsDev.Symbols.Location (ModuleLocation(..), Cabal(..))
+
+import Tool
+
+main :: IO ()
+main = toolMain "hsinspect" [
+	jsonCmd ["module"] ["module name"] "inspect installed module" [ghcOpts] inspectModule',
+	jsonCmd ["file"] ["source file"] "inspect file" [ghcOpts] inspectFile',
+	jsonCmd_ ["cabal"] ["project file"] "inspect .cabal file" inspectCabal']
+	where
+		ghcOpts = option_ ['g'] "ghc" (req "ghc options") "options to pass to GHC"
+		ghcs = askOpts "ghc"
+
+		inspectModule' opts [mname] = scanModule (ghcs opts) (CabalModule Cabal Nothing mname)
+		inspectModule' _ _ = toolError "Specify module name"
+
+		inspectFile' opts [fname] = scanModule (ghcs opts) (FileModule fname Nothing)
+		inspectFile' _ _ = toolError "Specify source file name"
+
+		inspectCabal' [fcabal] = readProject fcabal
+		inspectCabal' _ = toolError "Specify project .cabal file"