packages feed

hsdev 0.2.1.0 → 0.2.2.0

raw patch · 18 files changed

+571/−530 lines, 18 filesdep ~directorydep ~haskell-src-extsdep ~simple-log

Dependency ranges changed: directory, haskell-src-exts, simple-log

Files

hsdev.cabal view
@@ -1,5 +1,5 @@ name:                hsdev
-version:             0.2.1.0
+version:             0.2.2.0
 synopsis:            Haskell development library
 description:
   Haskell development library and tool with support of autocompletion, symbol info, go to declaration, find references, hayoo search etc.
@@ -128,9 +128,9 @@     fsnotify >= 0.2.1,
     ghc-paths >= 0.1.0,
     ghc-syb-utils >= 0.2.3,
-    haskell-src-exts >= 1.18.0 && < 1.19.0,
+    haskell-src-exts >= 1.18.0 && < 1.20.0,
     hdocs >= 0.5.0,
-    hformat >= 0.1,
+    hformat == 0.1.*,
     hlint >= 1.9.13 && < 2.0.0,
     HTTP >= 4000.2.0,
     lens >= 4.8,
@@ -143,11 +143,11 @@     process >= 1.2.0,
     regex-pcre-builtin >= 0.94,
     scientific >= 0.3,
-    simple-log >= 0.5.0,
+    simple-log >= 0.5 && < 0.6,
     syb >= 0.5.1,
     template-haskell,
     text >= 1.2.0,
-    text-region >= 0.1,
+    text-region >= 0.1 && < 0.2,
     time >= 1.5.0,
     transformers >= 0.4.0,
     transformers-base >= 0.4.0,
@@ -217,7 +217,7 @@     containers >= 0.5.0,
     data-default >= 0.5.0,
     directory >= 1.2.0,
-    haskell-src-exts >= 1.18.0 && < 1.19.0,
+    haskell-src-exts >= 1.18.0 && < 1.20.0,
     lens >= 4.8,
     mtl >= 2.2.0,
     optparse-applicative >= 0.11,
@@ -301,7 +301,7 @@     directory >= 1.2.0,
     filepath >= 1.4.0,
     containers >= 0.5.0,
-    hformat >= 0.1,
+    hformat == 0.1.*,
     hspec,
     lens >= 4.8,
     mtl >= 2.2.0,
src/HsDev/Client/Commands.hs view
@@ -209,7 +209,7 @@ runCommand (CheckLint fs ghcs') = toValue $ do
 	ensureUpToDate (Update.UpdateOptions [] ghcs' False False) fs
 	let
-		checkSome file fn = do
+		checkSome file fn = Log.scope "checkSome" $ do
 			m <- setFileSourceSession ghcs' file
 			inSessionGhc $ fn m
 	checkMsgs <- liftM concat $ mapM (\(FileSource f c) -> checkSome f (\m -> Check.check ghcs' m c)) fs
src/HsDev/Inspect.hs view
@@ -1,467 +1,467 @@-{-# LANGUAGE TypeSynonymInstances #-}--module HsDev.Inspect (-	analyzeModule, inspectDocsChunk, inspectDocs, inspectDocsGhc,-	inspectContents, contentsInspection,-	inspectFile, fileInspection,-	projectDirs, projectSources,-	inspectProject,-	getDefines,-	preprocess, preprocess_,--	module Control.Monad.Except-	) where--import Control.Arrow-import Control.Applicative-import Control.DeepSeq-import qualified Control.Exception as E-import Control.Lens (view, preview, set, over)-import Control.Lens.At (ix)-import Control.Monad-import Control.Monad.Except-import Data.Char (isSpace)-import Data.Function (on)-import Data.List-import Data.Map (Map)-import Data.Maybe (fromMaybe, mapMaybe, catMaybes, listToMaybe, isJust)-import Data.Ord (comparing)-import Data.String (IsString, fromString)-import Data.Text (Text)-import qualified Data.Text as T (unpack)-import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, getPOSIXTime)-import qualified Data.Map as M-import qualified Language.Haskell.Exts as H-import qualified Language.Preprocessor.Cpphs as Cpphs-import qualified System.Directory as Dir-import System.FilePath-import Data.Generics.Uniplate.Data-import HDocs.Haddock--import HsDev.Error-import HsDev.Symbols-import HsDev.Tools.Base-import HsDev.Tools.Ghc.Worker ()-import HsDev.Tools.HDocs (hdocsy, hdocs, hdocsProcess)-import HsDev.Util---- | Analize source contents-analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module-analyzeModule exts file source = case H.parseFileContentsWithMode (parseMode file exts) source' of-	H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason-	H.ParseOk (H.Module _ (Just (H.ModuleHead _ (H.ModuleName _ mname) _ mexports)) _ imports declarations) -> Right Module {-		_moduleName = fromString mname,-		_moduleDocs =  Nothing,-		_moduleLocation = ModuleSource Nothing,-		_moduleExports = fmap (concatMap getExports . getSpec) mexports,-		_moduleImports = map getImport imports,-		_moduleDeclarations = sortDeclarations $ getDecls declarations }-	_ -> Left "Unknown module"-	where-		-- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces-		source' = map untab source-		untab '\t' = ' '-		untab ch = ch-		getSpec (H.ExportSpecList _ es) = es---- | Analize source contents-analyzeModule_ :: [String] -> Maybe FilePath -> String -> Either String Module-analyzeModule_ exts file source = do-	mname <- parseModuleName source'-	return Module {-		_moduleName = fromString mname,-		_moduleDocs = Nothing,-		_moduleLocation = ModuleSource Nothing,-		_moduleExports = do-			H.PragmasAndModuleHead _ _ mhead <- parseModuleHead' source'-			H.ModuleHead _ _ _ mexports <- mhead-			H.ExportSpecList _ exports <- mexports-			return $ concatMap getExports exports,-		_moduleImports = map getImport $ mapMaybe (uncurry parseImport') parts,-		_moduleDeclarations = sortDeclarations $ getDecls $ mapMaybe (uncurry parseDecl') parts }-	where-		parts :: [(Int, String)]-		parts = zip offsets (map unlines parts') where-			parts' :: [[String]]-			parts' = unfoldr break' $ lines source'-			offsets = scanl (+) 0 $ map length parts'-		break' :: [String] -> Maybe ([String], [String])-		break' [] = Nothing-		break' (l:ls) = Just $ first (l:) $ span (maybe True isSpace . listToMaybe) ls--		parseModuleName :: String -> Either String String-		parseModuleName cts = maybe (Left "match fail") Right $ do-			g <- matchRx "^module\\s+([\\w\\.]+)" cts-			g 1--		parseDecl' :: Int -> String -> Maybe (H.Decl H.SrcSpanInfo)-		parseDecl' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $-			H.parseDeclWithMode (parseMode file exts) cts--		parseImport' :: Int -> String -> Maybe (H.ImportDecl H.SrcSpanInfo)-		parseImport' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $-			H.parseImportDeclWithMode (parseMode file exts) cts--		parseModuleHead' :: String -> Maybe (H.PragmasAndModuleHead H.SrcSpanInfo)-		parseModuleHead' = maybeResult . fmap H.unNonGreedy . H.parseWithMode (parseMode file exts)--		maybeResult :: H.ParseResult a -> Maybe a-		maybeResult (H.ParseFailed _ _) = Nothing-		maybeResult (H.ParseOk r) = Just r--		addOffset :: Int -> H.SrcSpan -> H.SrcSpan-		addOffset offset src = src { H.srcSpanStartLine = H.srcSpanStartLine src + offset, H.srcSpanEndLine = H.srcSpanEndLine src + offset }--		-- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces-		source' = map untab source-		untab '\t' = ' '-		untab ch = ch--parseMode :: Maybe FilePath -> [String] -> H.ParseMode-parseMode file exts = H.defaultParseMode {-	H.parseFilename = fromMaybe (H.parseFilename H.defaultParseMode) file,-	H.baseLanguage = H.Haskell2010,-	H.extensions = H.glasgowExts ++ map H.parseExtension exts,-	H.fixities = Just H.baseFixities }---- | Get exports-getExports :: H.ExportSpec H.SrcSpanInfo -> [Export]-getExports (H.EModuleContents _ (H.ModuleName _ m)) = [ExportModule $ fromString m]-getExports (H.EVar _ n) = [uncurry ExportName (identOfQName n) ThingNothing]-getExports (H.EAbs _ _ n) = [uncurry ExportName (identOfQName n) ThingNothing]-getExports (H.EThingWith _ _ n ns) = [uncurry ExportName (identOfQName n) $ ThingWith (map toStr ns)] where-	toStr :: H.CName H.SrcSpanInfo -> Text-	toStr (H.VarName _ cn) = identOfName cn-	toStr (H.ConName _ cn) = identOfName cn---- | Get import-getImport :: H.ImportDecl H.SrcSpanInfo -> Import-getImport d = Import-	(mname (H.importModule d))-	(H.importQualified d)-	(mname <$> H.importAs d)-	(importLst <$> H.importSpecs d)-	(Just $ toPosition $ H.ann d)-	where-		mname (H.ModuleName _ n) = fromString n-		importLst (H.ImportSpecList _ hiding specs) = ImportList hiding $ map impSpec specs-		impSpec (H.IVar _ n) = ImportSpec (identOfName n) ThingNothing-		impSpec (H.IAbs _ _ n) = ImportSpec (identOfName n) ThingNothing-		impSpec (H.IThingAll _ n) = ImportSpec (identOfName n) ThingAll-		impSpec (H.IThingWith _ n ns) = ImportSpec (identOfName n) $ ThingWith $ map identOfName (concatMap childrenBi ns :: [H.Name H.SrcSpanInfo])---- | Decl declarations-getDecls :: [H.Decl H.SrcSpanInfo] -> [Declaration]-getDecls decls =-	map mergeDecls .-	groupBy ((==) `on` view declarationName) .-	sortBy (comparing (view declarationName)) $-	concatMap getDecl decls ++ concatMap getDef decls-	where-		mergeDecls :: [Declaration] -> Declaration-		mergeDecls [] = error "Impossible"-		mergeDecls ds = Declaration-			(view declarationName $ head ds)-			Nothing-			Nothing-			(msum $ map (view declarationDocs) ds)-			(minimum <$> mapM (view declarationPosition) ds)-			(foldr1 mergeInfos $ map (view declaration) ds)--		mergeInfos :: DeclarationInfo -> DeclarationInfo -> DeclarationInfo-		mergeInfos (Function ln ld lr) (Function rn rd rr) = Function (ln `mplus` rn) (ld ++ rd) (lr `mplus` rr)-		mergeInfos l _ = l---- | Get local binds-getLocalDecls :: H.Decl H.SrcSpanInfo -> [Declaration]-getLocalDecls decl' = concatMap getDecls' binds' where-	binds' :: [H.Binds H.SrcSpanInfo]-	binds' = universeBi decl'-	getDecls' :: H.Binds H.SrcSpanInfo -> [Declaration]-	getDecls' (H.BDecls _ decls) = getDecls decls-	getDecls' _ = []---- | Get declaration and child declarations-getDecl :: H.Decl H.SrcSpanInfo -> [Declaration]-getDecl decl' = case decl' of-	H.TypeSig loc names typeSignature -> [mkFun loc n (Function (Just $ oneLinePrint typeSignature) [] Nothing) | n <- names]-	H.TypeDecl loc h _ -> [mkType loc (tyName h) Type (tyArgs h) `withDef` decl']-	H.DataDecl loc dataOrNew mctx h cons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getConDecl (tyName h) (tyArgs h)) cons-	H.GDataDecl loc dataOrNew mctx h _ gcons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getGConDecl) gcons-	H.ClassDecl loc ctx h _ _ -> [mkType loc (tyName h) (Class `withCtx` ctx) (tyArgs h) `withDef` decl']-	_ -> []-	where-		mkType :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> (TypeInfo -> DeclarationInfo) -> [H.TyVarBind H.SrcSpanInfo] -> Declaration-		mkType loc n ctor' args = setPosition loc $ decl (identOfName n) $ ctor' $ TypeInfo Nothing (map oneLinePrint args) Nothing []--		withDef :: H.Pretty a => Declaration -> a -> Declaration-		withDef tyDecl' tyDef = set (declaration . typeInfo . typeInfoDefinition) (Just $ prettyPrint tyDef) tyDecl'--		withCtx :: (TypeInfo -> DeclarationInfo) -> Maybe (H.Context H.SrcSpanInfo) -> TypeInfo -> DeclarationInfo-		withCtx ctor' mctx = ctor' . set typeInfoContext (fmap makeCtx mctx)--		ctor :: H.DataOrNew H.SrcSpanInfo -> TypeInfo -> DeclarationInfo-		ctor (H.DataType _) = Data-		ctor (H.NewType _) = NewType--		makeCtx ctx = fromString $ oneLinePrint ctx--		addRel :: H.Name H.SrcSpanInfo -> Declaration -> Declaration-		addRel n = set (declaration . related) (Just $ identOfName n)--		tyName :: H.DeclHead H.SrcSpanInfo -> H.Name H.SrcSpanInfo-		tyName (H.DHead _ n) = n-		tyName (H.DHInfix _ _ n) = n-		tyName (H.DHParen _ h) = tyName h-		tyName (H.DHApp _ h _) = tyName h--		tyArgs :: H.DeclHead H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo]-		tyArgs = universeBi---- | Get constructor and record fields declarations-getConDecl :: H.Name H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo] -> H.QualConDecl H.SrcSpanInfo -> [Declaration]-getConDecl t as (H.QualConDecl loc _ _ cdecl) = case cdecl of-	H.ConDecl _ n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ cts `tyFun` dataRes) [] Nothing)]-	H.InfixConDecl _ ct n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ (ct : [cts]) `tyFun` dataRes) [] Nothing)]-	H.RecDecl _ n fields -> mkFun loc n (Function (Just $ oneLinePrint $ map tyField fields `tyFun` dataRes) [] Nothing) : concatMap (getRec loc dataRes) fields-	where-		dataRes :: H.Type H.SrcSpanInfo-		dataRes = foldr (H.TyApp loc . H.TyVar loc . nameOf) (H.TyCon loc (H.UnQual loc t)) as where-			nameOf :: H.TyVarBind H.SrcSpanInfo -> H.Name H.SrcSpanInfo-			nameOf (H.KindedVar _ n' _) = n'-			nameOf (H.UnkindedVar _ n') = n'--tyField :: H.FieldDecl H.SrcSpanInfo -> H.Type H.SrcSpanInfo-tyField (H.FieldDecl _ _ t) = t---- | Get GADT constructor and record fields declarations-getGConDecl :: H.GadtDecl H.SrcSpanInfo -> [Declaration]-getGConDecl (H.GadtDecl loc n mfields r) = mkFun loc n (Function (Just $ oneLinePrint $ map tyField (fromMaybe [] mfields) `tyFun` r) [] Nothing) : concatMap (getRec loc r) (fromMaybe [] mfields)---- | Get record field declaration-getRec :: H.SrcSpanInfo -> H.Type H.SrcSpanInfo -> H.FieldDecl H.SrcSpanInfo -> [Declaration]-getRec loc t (H.FieldDecl _ ns rt) = [mkFun loc n (Function (Just $ oneLinePrint $ H.TyFun loc t rt) [] Nothing) | n <- ns]---- | Get definitions-getDef :: H.Decl H.SrcSpanInfo -> [Declaration]-getDef (H.FunBind _ []) = []-getDef d@(H.FunBind _ (m : _)) = [setPosition (H.ann m) $ decl (identOfName $ identOfMatch m) fun] where-	identOfMatch (H.Match _ n _ _ _) = n-	identOfMatch (H.InfixMatch _ _ n _ _ _) = n-	fun = Function Nothing (getLocalDecls d) Nothing-getDef d@(H.PatBind loc pat _ _) = map (\name -> setPosition loc (decl (identOfName name) (Function Nothing (getLocalDecls d) Nothing))) (names pat) where-	names :: H.Pat H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]-	names (H.PVar _ n) = [n]-	names (H.PNPlusK _ n _) = [n]-	names (H.PInfixApp _ l _ r) = names l ++ names r-	names (H.PApp _ _ ns) = concatMap names ns-	names (H.PTuple _ _ ns) = concatMap names ns-	names (H.PList _ ns) = concatMap names ns-	names (H.PParen _ n) = names n-	names (H.PRec _ _ pf) = concatMap fieldNames pf-	names (H.PAsPat _ n ns) = n : names ns-	names (H.PWildCard _) = []-	names (H.PIrrPat _ n) = names n-	names (H.PatTypeSig _ n _) = names n-	names (H.PViewPat _ _ n) = names n-	names (H.PBangPat _ n) = names n-	names _ = []--	fieldNames :: H.PatField H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]-	fieldNames (H.PFieldPat _ _ n) = names n-	fieldNames (H.PFieldPun _ n) = case n of-		H.Qual _ _ n' -> [n']-		H.UnQual _ n' -> [n']-		_ -> []-	fieldNames (H.PFieldWildcard _) = []-getDef _ = []---- | Make function declaration by location, name and function type-mkFun :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> DeclarationInfo -> Declaration-mkFun loc n = setPosition loc . decl (identOfName n)---- | Make function from arguments and result------ @[a, b, c...] `tyFun` r == a `TyFun` b `TyFun` c ... `TyFun` r@-tyFun :: [H.Type H.SrcSpanInfo] -> H.Type H.SrcSpanInfo -> H.Type H.SrcSpanInfo-tyFun as' r' = foldr (H.TyFun H.noSrcSpan) r' as'---- | Get name of qualified name-identOfQName :: H.QName H.SrcSpanInfo -> (Maybe Text, Text)-identOfQName (H.Qual _ (H.ModuleName _ mname) name) = (Just $ fromString mname, identOfName name)-identOfQName (H.UnQual _ name) = (Nothing, identOfName name)-identOfQName (H.Special _ sname) = (Nothing, fromString $ H.prettyPrint sname)---- | Get name of @H.Name@-identOfName :: H.Name H.SrcSpanInfo -> Text-identOfName name = fromString $ case name of-	H.Ident _ s -> s-	H.Symbol _ s -> s---- | Print something in one line-oneLinePrint :: (H.Pretty a, IsString s) => a -> s-oneLinePrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode---- | Print something-prettyPrint :: (H.Pretty a, IsString s) => a -> s-prettyPrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.PageMode }) mode' where-	mode' = H.PPHsMode {-		H.classIndent = 4,-		H.doIndent = 4,-		H.multiIfIndent = 4,-		H.caseIndent = 4,-		H.letIndent = 4,-		H.whereIndent = 4,-		H.onsideIndent = 2,-		H.spacing = False,-		H.layout = H.PPOffsideRule,-		H.linePragmas = False }---- | Convert @H.SrcSpanInfo@ to @Position-toPosition :: H.SrcSpanInfo -> Position-toPosition s = Position (H.startLine s) (H.startColumn s)---- | Set @Declaration@ position-setPosition :: H.SrcSpanInfo -> Declaration -> Declaration-setPosition loc = set declarationPosition (Just $ toPosition loc)---- | Adds documentation to declaration-addDoc :: Map String String -> Declaration -> Declaration-addDoc docsMap decl' = set declarationDocs (preview (ix (view declarationName decl')) docsMap') decl' where-	docsMap' = M.mapKeys fromString . M.map fromString $ docsMap---- | Adds documentation to all declarations in module-addDocs :: Map String String -> Module -> Module-addDocs docsMap = over moduleDeclarations (map $ addDoc docsMap)---- | Extract files docs and set them to declarations-inspectDocsChunk :: [String] -> [Module] -> IO [Module]-inspectDocsChunk opts ms = hsdevLiftIOWith (ToolError "hdocs") $ do-	docsMaps <- hdocsy (map (view moduleLocation) ms) opts-	return $ zipWith addDocs docsMaps ms---- | Extract file docs and set them to module declarations-inspectDocs :: [String] -> Module -> IO Module-inspectDocs opts m = do-	let-		hdocsWorkaround = False-	docsMap <- hsdevLiftIOWith (ToolError "hdocs") $ if hdocsWorkaround-		then hdocsProcess (fromMaybe (T.unpack $ view moduleName m) (preview (moduleLocation . moduleFile) m)) opts-		else liftM Just $ hdocs (view moduleLocation m) opts-	return $ maybe id addDocs docsMap m---- | Like @inspectDocs@, but in @Ghc@ monad-inspectDocsGhc :: [String] -> Module -> Ghc Module-inspectDocsGhc opts m = case view moduleLocation m of-	FileModule fpath _ -> do-		docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ hsdevLift $ readSourcesGhc opts [fpath]-		return $ maybe id addDocs docsMap m-	_ -> hsdevError $ ModuleNotSource (view moduleLocation m)---- | Inspect contents-inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule-inspectContents name defines opts cts = inspect (ModuleSource $ Just name) (contentsInspection cts opts) $ do-	cts' <- lift $ preprocess_ defines exts name cts-	analyzed <- ExceptT $ return $ analyzeModule exts (Just name) cts' <|> analyzeModule_ exts (Just name) cts'-	return $ set moduleLocation (ModuleSource $ Just name) analyzed-	where-		exts = mapMaybe flagExtension opts--contentsInspection :: String -> [String] -> ExceptT String IO Inspection-contentsInspection _ _ = return InspectionNone -- crc or smth---- | Inspect file-inspectFile :: [(String, String)] -> [String] -> FilePath -> Maybe String -> IO InspectedModule-inspectFile defines opts file mcts = hsdevLiftIO $ do-	proj <- locateProject file-	absFilename <- Dir.canonicalizePath file-	ex <- Dir.doesFileExist absFilename-	unless ex $ hsdevError $ FileNotFound absFilename-	inspect (FileModule absFilename proj) ((if isJust mcts then fileContentsInspection else fileInspection) absFilename opts) $ do-		-- docsMap <- liftE $ if hdocsWorkaround-		-- 	then hdocsProcess absFilename opts-		-- 	else liftM Just $ hdocs (FileModule absFilename Nothing) opts-		forced <- hsdevLiftWith InspectError $ ExceptT $ E.handle onError $ do-			analyzed <- liftM (\s -> analyzeModule exts (Just absFilename) s <|> analyzeModule_ exts (Just absFilename) s) $-				maybe (readFileUtf8 absFilename >>= preprocess_ defines exts file) return mcts-			force analyzed `deepseq` return analyzed-		-- return $ setLoc absFilename proj . maybe id addDocs docsMap $ forced-		return $ set moduleLocation (FileModule absFilename proj) forced-	where-		onError :: E.ErrorCall -> IO (Either String Module)-		onError = return . Left . show--		exts = mapMaybe flagExtension opts---- | File inspection data-fileInspection :: FilePath -> [String] -> IO Inspection-fileInspection f opts = do-	tm <- Dir.getModificationTime f-	return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ ordNub opts---- | File contents inspection data-fileContentsInspection :: FilePath -> [String] -> IO Inspection-fileContentsInspection _ opts = do-	tm <- getPOSIXTime-	return $ InspectionAt tm $ sort $ ordNub opts---- | Enumerate project dirs-projectDirs :: Project -> IO [Extensions FilePath]-projectDirs p = do-	p' <- loadProject p-	return $ ordNub $ map (fmap (normalise . (view projectPath p' </>))) $ maybe [] sourceDirs $ view projectDescription p'---- | Enumerate project source files-projectSources :: Project -> IO [Extensions FilePath]-projectSources p = do-	dirs <- projectDirs p-	let-		enumCabals = liftM (map takeDirectory . filter cabalFile) . traverseDirectory-		dirs' = map (view entity) dirs-	-- enum inner projects and dont consider them as part of this project-	subProjs <- liftM (delete (view projectPath p) . ordNub . concat) $ triesMap (enumCabals) dirs'-	let-		enumHs = liftM (filter thisProjectSource) . traverseDirectory-		thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs)-	liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (enumHs)) dirs---- | Inspect project-inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule])-inspectProject defines opts p = hsdevLiftIO $ do-	p' <- loadProject p-	srcs <- projectSources p'-	modules <- mapM inspectFile' srcs-	return (p', catMaybes modules)-	where-		inspectFile' exts = liftM return (inspectFile defines (opts ++ extensionsOpts exts) (view entity exts) Nothing) <|> return Nothing---- | Get actual defines-getDefines :: IO [(String, String)]-getDefines = E.handle onIO $ do-	tmp <- Dir.getTemporaryDirectory-	writeFile (tmp </> "defines.hs") ""-	_ <- runWait "ghc" ["-E", "-optP-dM", "-cpp", tmp </> "defines.hs"] ""-	cts <- readFileUtf8 (tmp </> "defines.hspp")-	Dir.removeFile (tmp </> "defines.hs")-	Dir.removeFile (tmp </> "defines.hspp")-	return $ mapMaybe (\g -> (,) <$> g 1 <*> g 2) $ mapMaybe (matchRx rx) $ lines cts-	where-		rx = "#define ([^\\s]+) (.*)"-		onIO :: E.IOException -> IO [(String, String)]-		onIO _ = return []--preprocess :: [(String, String)] -> FilePath -> String -> IO String-preprocess defines fpath cts = do-	cts' <- E.catch (Cpphs.cppIfdef fpath defines [] Cpphs.defaultBoolOptions cts) onIOError-	return $ unlines $ map snd cts'-	where-		onIOError :: E.IOException -> IO [(Cpphs.Posn, String)]-		onIOError _ = return []--preprocess_ :: [(String, String)] -> [String] -> FilePath -> String -> IO String-preprocess_ defines exts fpath cts-	| hasCPP = preprocess defines fpath cts-	| otherwise = return cts-	where-		exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions cts)-		hasCPP = H.EnableExtension H.CPP `elem` exts'+{-# LANGUAGE TypeSynonymInstances #-}
+
+module HsDev.Inspect (
+	analyzeModule, inspectDocsChunk, inspectDocs, inspectDocsGhc,
+	inspectContents, contentsInspection,
+	inspectFile, fileInspection,
+	projectDirs, projectSources,
+	inspectProject,
+	getDefines,
+	preprocess, preprocess_,
+
+	module Control.Monad.Except
+	) where
+
+import Control.Arrow
+import Control.Applicative
+import Control.DeepSeq
+import qualified Control.Exception as E
+import Control.Lens (view, preview, set, over)
+import Control.Lens.At (ix)
+import Control.Monad
+import Control.Monad.Except
+import Data.Char (isSpace)
+import Data.Function (on)
+import Data.List
+import Data.Map (Map)
+import Data.Maybe (fromMaybe, mapMaybe, catMaybes, listToMaybe, isJust)
+import Data.Ord (comparing)
+import Data.String (IsString, fromString)
+import Data.Text (Text)
+import qualified Data.Text as T (unpack)
+import Data.Time.Clock.POSIX (utcTimeToPOSIXSeconds, getPOSIXTime)
+import qualified Data.Map as M
+import qualified Language.Haskell.Exts as H
+import qualified Language.Preprocessor.Cpphs as Cpphs
+import qualified System.Directory as Dir
+import System.FilePath
+import Data.Generics.Uniplate.Data
+import HDocs.Haddock
+
+import HsDev.Error
+import HsDev.Symbols
+import HsDev.Tools.Base
+import HsDev.Tools.Ghc.Worker ()
+import HsDev.Tools.HDocs (hdocsy, hdocs, hdocsProcess)
+import HsDev.Util
+
+-- | Analize source contents
+analyzeModule :: [String] -> Maybe FilePath -> String -> Either String Module
+analyzeModule exts file source = case H.parseFileContentsWithMode (parseMode file exts) source' of
+	H.ParseFailed loc reason -> Left $ "Parse failed at " ++ show loc ++ ": " ++ reason
+	H.ParseOk (H.Module _ (Just (H.ModuleHead _ (H.ModuleName _ mname) _ mexports)) _ imports declarations) -> Right Module {
+		_moduleName = fromString mname,
+		_moduleDocs =  Nothing,
+		_moduleLocation = ModuleSource Nothing,
+		_moduleExports = fmap (concatMap getExports . getSpec) mexports,
+		_moduleImports = map getImport imports,
+		_moduleDeclarations = sortDeclarations $ getDecls declarations }
+	_ -> Left "Unknown module"
+	where
+		-- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces
+		source' = map untab source
+		untab '\t' = ' '
+		untab ch = ch
+		getSpec (H.ExportSpecList _ es) = es
+
+-- | Analize source contents
+analyzeModule_ :: [String] -> Maybe FilePath -> String -> Either String Module
+analyzeModule_ exts file source = do
+	mname <- parseModuleName source'
+	return Module {
+		_moduleName = fromString mname,
+		_moduleDocs = Nothing,
+		_moduleLocation = ModuleSource Nothing,
+		_moduleExports = do
+			H.PragmasAndModuleHead _ _ mhead <- parseModuleHead' source'
+			H.ModuleHead _ _ _ mexports <- mhead
+			H.ExportSpecList _ exports <- mexports
+			return $ concatMap getExports exports,
+		_moduleImports = map getImport $ mapMaybe (uncurry parseImport') parts,
+		_moduleDeclarations = sortDeclarations $ getDecls $ mapMaybe (uncurry parseDecl') parts }
+	where
+		parts :: [(Int, String)]
+		parts = zip offsets (map unlines parts') where
+			parts' :: [[String]]
+			parts' = unfoldr break' $ lines source'
+			offsets = scanl (+) 0 $ map length parts'
+		break' :: [String] -> Maybe ([String], [String])
+		break' [] = Nothing
+		break' (l:ls) = Just $ first (l:) $ span (maybe True isSpace . listToMaybe) ls
+
+		parseModuleName :: String -> Either String String
+		parseModuleName cts = maybe (Left "match fail") Right $ do
+			g <- matchRx "^module\\s+([\\w\\.]+)" cts
+			g 1
+
+		parseDecl' :: Int -> String -> Maybe (H.Decl H.SrcSpanInfo)
+		parseDecl' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $
+			H.parseDeclWithMode (parseMode file exts) cts
+
+		parseImport' :: Int -> String -> Maybe (H.ImportDecl H.SrcSpanInfo)
+		parseImport' offset cts = maybeResult $ fmap (transformBi $ addOffset offset) $
+			H.parseImportDeclWithMode (parseMode file exts) cts
+
+		parseModuleHead' :: String -> Maybe (H.PragmasAndModuleHead H.SrcSpanInfo)
+		parseModuleHead' = maybeResult . fmap H.unNonGreedy . H.parseWithMode (parseMode file exts)
+
+		maybeResult :: H.ParseResult a -> Maybe a
+		maybeResult (H.ParseFailed _ _) = Nothing
+		maybeResult (H.ParseOk r) = Just r
+
+		addOffset :: Int -> H.SrcSpan -> H.SrcSpan
+		addOffset offset src = src { H.srcSpanStartLine = H.srcSpanStartLine src + offset, H.srcSpanEndLine = H.srcSpanEndLine src + offset }
+
+		-- Replace all tabs to spaces to make SrcLoc valid, otherwise it treats tab as 8 spaces
+		source' = map untab source
+		untab '\t' = ' '
+		untab ch = ch
+
+parseMode :: Maybe FilePath -> [String] -> H.ParseMode
+parseMode file exts = H.defaultParseMode {
+	H.parseFilename = fromMaybe (H.parseFilename H.defaultParseMode) file,
+	H.baseLanguage = H.Haskell2010,
+	H.extensions = H.glasgowExts ++ map H.parseExtension exts,
+	H.fixities = Just H.baseFixities }
+
+-- | Get exports
+getExports :: H.ExportSpec H.SrcSpanInfo -> [Export]
+getExports (H.EModuleContents _ (H.ModuleName _ m)) = [ExportModule $ fromString m]
+getExports (H.EVar _ n) = [uncurry ExportName (identOfQName n) ThingNothing]
+getExports (H.EAbs _ _ n) = [uncurry ExportName (identOfQName n) ThingNothing]
+getExports (H.EThingWith _ _ n ns) = [uncurry ExportName (identOfQName n) $ ThingWith (map toStr ns)] where
+	toStr :: H.CName H.SrcSpanInfo -> Text
+	toStr (H.VarName _ cn) = identOfName cn
+	toStr (H.ConName _ cn) = identOfName cn
+
+-- | Get import
+getImport :: H.ImportDecl H.SrcSpanInfo -> Import
+getImport d = Import
+	(mname (H.importModule d))
+	(H.importQualified d)
+	(mname <$> H.importAs d)
+	(importLst <$> H.importSpecs d)
+	(Just $ toPosition $ H.ann d)
+	where
+		mname (H.ModuleName _ n) = fromString n
+		importLst (H.ImportSpecList _ hiding specs) = ImportList hiding $ map impSpec specs
+		impSpec (H.IVar _ n) = ImportSpec (identOfName n) ThingNothing
+		impSpec (H.IAbs _ _ n) = ImportSpec (identOfName n) ThingNothing
+		impSpec (H.IThingAll _ n) = ImportSpec (identOfName n) ThingAll
+		impSpec (H.IThingWith _ n ns) = ImportSpec (identOfName n) $ ThingWith $ map identOfName (concatMap childrenBi ns :: [H.Name H.SrcSpanInfo])
+
+-- | Decl declarations
+getDecls :: [H.Decl H.SrcSpanInfo] -> [Declaration]
+getDecls decls =
+	map mergeDecls .
+	groupBy ((==) `on` view declarationName) .
+	sortBy (comparing (view declarationName)) $
+	concatMap getDecl decls ++ concatMap getDef decls
+	where
+		mergeDecls :: [Declaration] -> Declaration
+		mergeDecls [] = error "Impossible"
+		mergeDecls ds = Declaration
+			(view declarationName $ head ds)
+			Nothing
+			Nothing
+			(msum $ map (view declarationDocs) ds)
+			(minimum <$> mapM (view declarationPosition) ds)
+			(foldr1 mergeInfos $ map (view declaration) ds)
+
+		mergeInfos :: DeclarationInfo -> DeclarationInfo -> DeclarationInfo
+		mergeInfos (Function ln ld lr) (Function rn rd rr) = Function (ln `mplus` rn) (ld ++ rd) (lr `mplus` rr)
+		mergeInfos l _ = l
+
+-- | Get local binds
+getLocalDecls :: H.Decl H.SrcSpanInfo -> [Declaration]
+getLocalDecls decl' = concatMap getDecls' binds' where
+	binds' :: [H.Binds H.SrcSpanInfo]
+	binds' = universeBi decl'
+	getDecls' :: H.Binds H.SrcSpanInfo -> [Declaration]
+	getDecls' (H.BDecls _ decls) = getDecls decls
+	getDecls' _ = []
+
+-- | Get declaration and child declarations
+getDecl :: H.Decl H.SrcSpanInfo -> [Declaration]
+getDecl decl' = case decl' of
+	H.TypeSig loc names typeSignature -> [mkFun loc n (Function (Just $ oneLinePrint typeSignature) [] Nothing) | n <- names]
+	H.TypeDecl loc h _ -> [mkType loc (tyName h) Type (tyArgs h) `withDef` decl']
+	H.DataDecl loc dataOrNew mctx h cons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getConDecl (tyName h) (tyArgs h)) cons
+	H.GDataDecl loc dataOrNew mctx h _ gcons _ -> (mkType loc (tyName h) (ctor dataOrNew `withCtx` mctx) (tyArgs h) `withDef` decl') : concatMap (map (addRel $ tyName h) . getGConDecl) gcons
+	H.ClassDecl loc ctx h _ _ -> [mkType loc (tyName h) (Class `withCtx` ctx) (tyArgs h) `withDef` decl']
+	_ -> []
+	where
+		mkType :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> (TypeInfo -> DeclarationInfo) -> [H.TyVarBind H.SrcSpanInfo] -> Declaration
+		mkType loc n ctor' args = setPosition loc $ decl (identOfName n) $ ctor' $ TypeInfo Nothing (map oneLinePrint args) Nothing []
+
+		withDef :: H.Pretty a => Declaration -> a -> Declaration
+		withDef tyDecl' tyDef = set (declaration . typeInfo . typeInfoDefinition) (Just $ prettyPrint tyDef) tyDecl'
+
+		withCtx :: (TypeInfo -> DeclarationInfo) -> Maybe (H.Context H.SrcSpanInfo) -> TypeInfo -> DeclarationInfo
+		withCtx ctor' mctx = ctor' . set typeInfoContext (fmap makeCtx mctx)
+
+		ctor :: H.DataOrNew H.SrcSpanInfo -> TypeInfo -> DeclarationInfo
+		ctor (H.DataType _) = Data
+		ctor (H.NewType _) = NewType
+
+		makeCtx ctx = fromString $ oneLinePrint ctx
+
+		addRel :: H.Name H.SrcSpanInfo -> Declaration -> Declaration
+		addRel n = set (declaration . related) (Just $ identOfName n)
+
+		tyName :: H.DeclHead H.SrcSpanInfo -> H.Name H.SrcSpanInfo
+		tyName (H.DHead _ n) = n
+		tyName (H.DHInfix _ _ n) = n
+		tyName (H.DHParen _ h) = tyName h
+		tyName (H.DHApp _ h _) = tyName h
+
+		tyArgs :: H.DeclHead H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo]
+		tyArgs = universeBi
+
+-- | Get constructor and record fields declarations
+getConDecl :: H.Name H.SrcSpanInfo -> [H.TyVarBind H.SrcSpanInfo] -> H.QualConDecl H.SrcSpanInfo -> [Declaration]
+getConDecl t as (H.QualConDecl loc _ _ cdecl) = case cdecl of
+	H.ConDecl _ n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ cts `tyFun` dataRes) [] Nothing)]
+	H.InfixConDecl _ ct n cts -> [mkFun loc n (Function (Just $ oneLinePrint $ (ct : [cts]) `tyFun` dataRes) [] Nothing)]
+	H.RecDecl _ n fields -> mkFun loc n (Function (Just $ oneLinePrint $ map tyField fields `tyFun` dataRes) [] Nothing) : concatMap (getRec loc dataRes) fields
+	where
+		dataRes :: H.Type H.SrcSpanInfo
+		dataRes = foldr (H.TyApp loc . H.TyVar loc . nameOf) (H.TyCon loc (H.UnQual loc t)) as where
+			nameOf :: H.TyVarBind H.SrcSpanInfo -> H.Name H.SrcSpanInfo
+			nameOf (H.KindedVar _ n' _) = n'
+			nameOf (H.UnkindedVar _ n') = n'
+
+tyField :: H.FieldDecl H.SrcSpanInfo -> H.Type H.SrcSpanInfo
+tyField (H.FieldDecl _ _ t) = t
+
+-- | Get GADT constructor and record fields declarations
+getGConDecl :: H.GadtDecl H.SrcSpanInfo -> [Declaration]
+getGConDecl (H.GadtDecl loc n mfields r) = mkFun loc n (Function (Just $ oneLinePrint $ map tyField (fromMaybe [] mfields) `tyFun` r) [] Nothing) : concatMap (getRec loc r) (fromMaybe [] mfields)
+
+-- | Get record field declaration
+getRec :: H.SrcSpanInfo -> H.Type H.SrcSpanInfo -> H.FieldDecl H.SrcSpanInfo -> [Declaration]
+getRec loc t (H.FieldDecl _ ns rt) = [mkFun loc n (Function (Just $ oneLinePrint $ H.TyFun loc t rt) [] Nothing) | n <- ns]
+
+-- | Get definitions
+getDef :: H.Decl H.SrcSpanInfo -> [Declaration]
+getDef (H.FunBind _ []) = []
+getDef d@(H.FunBind _ (m : _)) = [setPosition (H.ann m) $ decl (identOfName $ identOfMatch m) fun] where
+	identOfMatch (H.Match _ n _ _ _) = n
+	identOfMatch (H.InfixMatch _ _ n _ _ _) = n
+	fun = Function Nothing (getLocalDecls d) Nothing
+getDef d@(H.PatBind loc pat _ _) = map (\name -> setPosition loc (decl (identOfName name) (Function Nothing (getLocalDecls d) Nothing))) (names pat) where
+	names :: H.Pat H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]
+	names (H.PVar _ n) = [n]
+	names (H.PNPlusK _ n _) = [n]
+	names (H.PInfixApp _ l _ r) = names l ++ names r
+	names (H.PApp _ _ ns) = concatMap names ns
+	names (H.PTuple _ _ ns) = concatMap names ns
+	names (H.PList _ ns) = concatMap names ns
+	names (H.PParen _ n) = names n
+	names (H.PRec _ _ pf) = concatMap fieldNames pf
+	names (H.PAsPat _ n ns) = n : names ns
+	names (H.PWildCard _) = []
+	names (H.PIrrPat _ n) = names n
+	names (H.PatTypeSig _ n _) = names n
+	names (H.PViewPat _ _ n) = names n
+	names (H.PBangPat _ n) = names n
+	names _ = []
+
+	fieldNames :: H.PatField H.SrcSpanInfo -> [H.Name H.SrcSpanInfo]
+	fieldNames (H.PFieldPat _ _ n) = names n
+	fieldNames (H.PFieldPun _ n) = case n of
+		H.Qual _ _ n' -> [n']
+		H.UnQual _ n' -> [n']
+		_ -> []
+	fieldNames (H.PFieldWildcard _) = []
+getDef _ = []
+
+-- | Make function declaration by location, name and function type
+mkFun :: H.SrcSpanInfo -> H.Name H.SrcSpanInfo -> DeclarationInfo -> Declaration
+mkFun loc n = setPosition loc . decl (identOfName n)
+
+-- | Make function from arguments and result
+--
+-- @[a, b, c...] `tyFun` r == a `TyFun` b `TyFun` c ... `TyFun` r@
+tyFun :: [H.Type H.SrcSpanInfo] -> H.Type H.SrcSpanInfo -> H.Type H.SrcSpanInfo
+tyFun as' r' = foldr (H.TyFun H.noSrcSpan) r' as'
+
+-- | Get name of qualified name
+identOfQName :: H.QName H.SrcSpanInfo -> (Maybe Text, Text)
+identOfQName (H.Qual _ (H.ModuleName _ mname) name) = (Just $ fromString mname, identOfName name)
+identOfQName (H.UnQual _ name) = (Nothing, identOfName name)
+identOfQName (H.Special _ sname) = (Nothing, fromString $ H.prettyPrint sname)
+
+-- | Get name of @H.Name@
+identOfName :: H.Name H.SrcSpanInfo -> Text
+identOfName name = fromString $ case name of
+	H.Ident _ s -> s
+	H.Symbol _ s -> s
+
+-- | Print something in one line
+oneLinePrint :: (H.Pretty a, IsString s) => a -> s
+oneLinePrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.OneLineMode }) H.defaultMode
+
+-- | Print something
+prettyPrint :: (H.Pretty a, IsString s) => a -> s
+prettyPrint = fromString . H.prettyPrintStyleMode (H.style { H.mode = H.PageMode }) mode' where
+	mode' = H.PPHsMode {
+		H.classIndent = 4,
+		H.doIndent = 4,
+		H.multiIfIndent = 4,
+		H.caseIndent = 4,
+		H.letIndent = 4,
+		H.whereIndent = 4,
+		H.onsideIndent = 2,
+		H.spacing = False,
+		H.layout = H.PPOffsideRule,
+		H.linePragmas = False }
+
+-- | Convert @H.SrcSpanInfo@ to @Position
+toPosition :: H.SrcSpanInfo -> Position
+toPosition s = Position (H.startLine s) (H.startColumn s)
+
+-- | Set @Declaration@ position
+setPosition :: H.SrcSpanInfo -> Declaration -> Declaration
+setPosition loc = set declarationPosition (Just $ toPosition loc)
+
+-- | Adds documentation to declaration
+addDoc :: Map String String -> Declaration -> Declaration
+addDoc docsMap decl' = set declarationDocs (preview (ix (view declarationName decl')) docsMap') decl' where
+	docsMap' = M.mapKeys fromString . M.map fromString $ docsMap
+
+-- | Adds documentation to all declarations in module
+addDocs :: Map String String -> Module -> Module
+addDocs docsMap = over moduleDeclarations (map $ addDoc docsMap)
+
+-- | Extract files docs and set them to declarations
+inspectDocsChunk :: [String] -> [Module] -> IO [Module]
+inspectDocsChunk opts ms = hsdevLiftIOWith (ToolError "hdocs") $ do
+	docsMaps <- hdocsy (map (view moduleLocation) ms) opts
+	return $ zipWith addDocs docsMaps ms
+
+-- | Extract file docs and set them to module declarations
+inspectDocs :: [String] -> Module -> IO Module
+inspectDocs opts m = do
+	let
+		hdocsWorkaround = False
+	docsMap <- if hdocsWorkaround
+		then hdocsProcess (fromMaybe (T.unpack $ view moduleName m) (preview (moduleLocation . moduleFile) m)) opts
+		else liftM Just $ hdocs (view moduleLocation m) opts
+	return $ maybe id addDocs docsMap m
+
+-- | Like @inspectDocs@, but in @Ghc@ monad
+inspectDocsGhc :: [String] -> Module -> Ghc Module
+inspectDocsGhc opts m = case view moduleLocation m of
+	FileModule fpath _ -> do
+		docsMap <- liftM (fmap (formatDocs . snd) . listToMaybe) $ hsdevLift $ readSourcesGhc opts [fpath]
+		return $ maybe id addDocs docsMap m
+	_ -> hsdevError $ ModuleNotSource (view moduleLocation m)
+
+-- | Inspect contents
+inspectContents :: String -> [(String, String)] -> [String] -> String -> ExceptT String IO InspectedModule
+inspectContents name defines opts cts = inspect (ModuleSource $ Just name) (contentsInspection cts opts) $ do
+	cts' <- lift $ preprocess_ defines exts name cts
+	analyzed <- ExceptT $ return $ analyzeModule exts (Just name) cts' <|> analyzeModule_ exts (Just name) cts'
+	return $ set moduleLocation (ModuleSource $ Just name) analyzed
+	where
+		exts = mapMaybe flagExtension opts
+
+contentsInspection :: String -> [String] -> ExceptT String IO Inspection
+contentsInspection _ _ = return InspectionNone -- crc or smth
+
+-- | Inspect file
+inspectFile :: [(String, String)] -> [String] -> FilePath -> Maybe String -> IO InspectedModule
+inspectFile defines opts file mcts = hsdevLiftIO $ do
+	proj <- locateProject file
+	absFilename <- Dir.canonicalizePath file
+	ex <- Dir.doesFileExist absFilename
+	unless ex $ hsdevError $ FileNotFound absFilename
+	inspect (FileModule absFilename proj) ((if isJust mcts then fileContentsInspection else fileInspection) absFilename opts) $ do
+		-- docsMap <- liftE $ if hdocsWorkaround
+		-- 	then hdocsProcess absFilename opts
+		-- 	else liftM Just $ hdocs (FileModule absFilename Nothing) opts
+		forced <- hsdevLiftWith InspectError $ ExceptT $ E.handle onError $ do
+			analyzed <- liftM (\s -> analyzeModule exts (Just absFilename) s <|> analyzeModule_ exts (Just absFilename) s) $
+				maybe (readFileUtf8 absFilename >>= preprocess_ defines exts file) return mcts
+			force analyzed `deepseq` return analyzed
+		-- return $ setLoc absFilename proj . maybe id addDocs docsMap $ forced
+		return $ set moduleLocation (FileModule absFilename proj) forced
+	where
+		onError :: E.ErrorCall -> IO (Either String Module)
+		onError = return . Left . show
+
+		exts = mapMaybe flagExtension opts
+
+-- | File inspection data
+fileInspection :: FilePath -> [String] -> IO Inspection
+fileInspection f opts = do
+	tm <- Dir.getModificationTime f
+	return $ InspectionAt (utcTimeToPOSIXSeconds tm) $ sort $ ordNub opts
+
+-- | File contents inspection data
+fileContentsInspection :: FilePath -> [String] -> IO Inspection
+fileContentsInspection _ opts = do
+	tm <- getPOSIXTime
+	return $ InspectionAt tm $ sort $ ordNub opts
+
+-- | Enumerate project dirs
+projectDirs :: Project -> IO [Extensions FilePath]
+projectDirs p = do
+	p' <- loadProject p
+	return $ ordNub $ map (fmap (normalise . (view projectPath p' </>))) $ maybe [] sourceDirs $ view projectDescription p'
+
+-- | Enumerate project source files
+projectSources :: Project -> IO [Extensions FilePath]
+projectSources p = do
+	dirs <- projectDirs p
+	let
+		enumCabals = liftM (map takeDirectory . filter cabalFile) . traverseDirectory
+		dirs' = map (view entity) dirs
+	-- enum inner projects and dont consider them as part of this project
+	subProjs <- liftM (delete (view projectPath p) . ordNub . concat) $ triesMap (enumCabals) dirs'
+	let
+		enumHs = liftM (filter thisProjectSource) . traverseDirectory
+		thisProjectSource h = haskellSource h && not (any (`isParent` h) subProjs)
+	liftM (ordNub . concat) $ triesMap (liftM sequenceA . traverse (enumHs)) dirs
+
+-- | Inspect project
+inspectProject :: [(String, String)] -> [String] -> Project -> IO (Project, [InspectedModule])
+inspectProject defines opts p = hsdevLiftIO $ do
+	p' <- loadProject p
+	srcs <- projectSources p'
+	modules <- mapM inspectFile' srcs
+	return (p', catMaybes modules)
+	where
+		inspectFile' exts = liftM return (inspectFile defines (opts ++ extensionsOpts exts) (view entity exts) Nothing) <|> return Nothing
+
+-- | Get actual defines
+getDefines :: IO [(String, String)]
+getDefines = E.handle onIO $ do
+	tmp <- Dir.getTemporaryDirectory
+	writeFile (tmp </> "defines.hs") ""
+	_ <- runWait "ghc" ["-E", "-optP-dM", "-cpp", tmp </> "defines.hs"] ""
+	cts <- readFileUtf8 (tmp </> "defines.hspp")
+	Dir.removeFile (tmp </> "defines.hs")
+	Dir.removeFile (tmp </> "defines.hspp")
+	return $ mapMaybe (\g -> (,) <$> g 1 <*> g 2) $ mapMaybe (matchRx rx) $ lines cts
+	where
+		rx = "#define ([^\\s]+) (.*)"
+		onIO :: E.IOException -> IO [(String, String)]
+		onIO _ = return []
+
+preprocess :: [(String, String)] -> FilePath -> String -> IO String
+preprocess defines fpath cts = do
+	cts' <- E.catch (Cpphs.cppIfdef fpath defines [] Cpphs.defaultBoolOptions cts) onIOError
+	return $ unlines $ map snd cts'
+	where
+		onIOError :: E.IOException -> IO [(Cpphs.Posn, String)]
+		onIOError _ = return []
+
+preprocess_ :: [(String, String)] -> [String] -> FilePath -> String -> IO String
+preprocess_ defines exts fpath cts
+	| hasCPP = preprocess defines fpath cts
+	| otherwise = return cts
+	where
+		exts' = map H.parseExtension exts ++ maybe [] snd (H.readExtensions cts)
+		hasCPP = H.EnableExtension H.CPP `elem` exts'
src/HsDev/Project.hs view
@@ -4,7 +4,7 @@ 	infoSourceDirsDef,
 	readProject, loadProject,
 	withExtensions,
-	infos, inTarget, fileTargets, findSourceDir, sourceDirs,
+	infos, inTarget, fileTarget, fileTargets, findSourceDir, sourceDirs,
 	targetOpts,
 
 	-- * Helpers
@@ -112,7 +112,11 @@ 
 -- | 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) $ view infoSourceDirsDef info
+inTarget src info = any (`isParent` src) $ view infoSourceDirsDef info
+
+-- | Get first target for source file
+fileTarget :: Project -> FilePath -> Maybe Info
+fileTarget p f = listToMaybe $ fileTargets p f
 
 -- | Get possible targets for source file
 -- There can be many candidates in case of module related to several executables or tests
src/HsDev/Sandbox.hs view
@@ -19,6 +19,7 @@ import Data.Aeson
 import Data.Maybe (isJust, fromMaybe)
 import Data.List ((\\))
+import Data.String (fromString)
 import qualified Data.Text as T (unpack)
 import Distribution.Compiler
 import Distribution.System
@@ -26,6 +27,7 @@ import System.FilePath
 import System.Directory
 import System.Log.Simple (MonadLog(..))
+import qualified System.Log.Simple as Log
 
 import System.Directory.Paths
 import HsDev.PackageDb
@@ -34,7 +36,7 @@ import HsDev.Symbols (moduleOpts)
 import HsDev.Symbols.Types (Module(..), ModuleLocation(..), moduleLocation)
 import HsDev.Tools.Ghc.Compat as Compat
-import HsDev.Util (searchPath)
+import HsDev.Util (searchPath, directoryContents)
 
 import qualified Packages as GHC
 
@@ -83,7 +85,7 @@ 	isDir <- doesDirectoryExist fpath'
 	if isDir
 		then do
-			dirs <- liftM ((fpath' :) . map (fpath' </>) . (\\ [".", ".."])) $ getDirectoryContents fpath'
+			dirs <- liftM (fpath' :) $ directoryContents fpath'
 			return $ msum $ map sandboxFromDir dirs
 		else return Nothing
 	where
@@ -141,7 +143,7 @@ getModuleOpts :: MonadLog m => [String] -> Module -> m [String]
 getModuleOpts opts m = do
 	pdbs <- case view moduleLocation m of
-		FileModule fpath _ -> searchPackageDbStack fpath
+		FileModule fpath p -> searchPackageDbStack fpath
 		InstalledModule pdb _ _ -> restorePackageDbStack pdb
 		ModuleSource _ -> return userDb
 	pkgs <- browsePackages opts pdbs
src/HsDev/Server/Commands.hs view
@@ -45,6 +45,7 @@ import qualified HsDev.Database.Async as DB
 import HsDev.Server.Base
 import HsDev.Server.Types
+import HsDev.Tools.Base (runTool_)
 import HsDev.Error
 import HsDev.Util
 import HsDev.Version
@@ -54,7 +55,6 @@ import Data.Char
 import Data.List
 import System.Environment
-import System.Process
 import System.Win32.FileMapping.Memory (withMapFile, readMapFile)
 import System.Win32.FileMapping.NamePool
 import System.Win32.PowerShell (escape, quote, quoteDouble)
@@ -123,9 +123,9 @@ 			~~ ("process" %= escape quote myExe)
 			~~ ("args" %= intercalate ", " (map biescape args))
 			~~ ("dir" %= escape quote curDir)
-	r <- readProcess "powershell" [
+	r <- runTool_ "powershell" [
 		"-Command",
-		script] ""
+		script]
 	if all isSpace r
 		then putStrLn $ "Server started at port {}" ~~ serverPort sopts
 		else mapM_ putStrLn [
@@ -167,11 +167,11 @@ 				bind s (sockAddr (serverPort sopts) addr')
 				listen s maxListenQueue
 			forever $ logAsync (Log.log Log.Fatal . fromString) $ logIO "exception: " (Log.log Log.Error . fromString) $ do
-				Log.log Log.Trace "accepting connection"
+				Log.log Log.Trace "accepting connection..."
 				liftIO $ signalQSem q
-				s' <- liftIO $ fst <$> accept s
-				Log.log Log.Trace $ "accepted {}" ~~ show s'
-				void $ liftIO $ forkIO $ withSession session $ Log.scope (T.pack $ show s') $
+				(s', addr') <- liftIO $ accept s
+				Log.log Log.Trace $ "accepted {}" ~~ show addr'
+				void $ liftIO $ forkIO $ withSession session $ Log.scope (T.pack $ show addr') $
 					logAsync (Log.log Log.Fatal . fromString) $ logIO "exception: " (Log.log Log.Error . fromString) $
 						flip finally (liftIO $ close s') $
 							bracket (liftIO newEmptyMVar) (liftIO . (`putMVar` ())) $ \done -> do
@@ -180,23 +180,23 @@ 									timeoutWait = withSession session $ do
 										notDone <- liftIO $ isEmptyMVar done
 										when notDone $ do
-											Log.log Log.Trace $ "waiting for {} to complete" ~~ show s'
+											Log.log Log.Trace $ "waiting for {} to complete" ~~ show addr'
 											waitAsync <- liftIO $ async $ do
 												threadDelay 1000000
 												killThread me
 											liftIO $ void $ waitCatch waitAsync
 								liftIO $ F.putChan clientChan timeoutWait
-								processClientSocket s'
+								processClientSocket (show addr') s'
 
-	Log.log Log.Trace "waiting for starting accept thread"
+	Log.log Log.Trace "waiting for starting accept thread..."
 	liftIO $ waitQSem q
 	Log.log Log.Info $ "Server started at port {}" ~~ serverPort sopts
-	Log.log Log.Trace "waiting for accept thread"
+	Log.log Log.Trace "waiting for accept thread..."
 	serverWait
 	Log.log Log.Trace "accept thread stopped"
 	liftIO $ unlink (serverPort sopts)
 	askSession sessionDatabase >>= liftIO . DB.readAsync >>= writeCache sopts
-	Log.log Log.Trace "waiting for clients"
+	Log.log Log.Trace "waiting for clients..."
 	liftIO (F.stopChan clientChan) >>= sequence_
 	Log.log Log.Info "server stopped"
 runServerCommand (Stop copts) = runServerCommand (Remote copts False Exit)
@@ -290,7 +290,7 @@ -- | Process client, listen for requests and process them
 processClient :: SessionMonad m => String -> F.Chan ByteString -> (ByteString -> IO ()) -> m ()
 processClient name rchan send' = do
-	Log.log Log.Info $ "{} connected" ~~ name
+	Log.log Log.Info "connected"
 	respChan <- liftIO newChan
 	liftIO $ void $ forkIO $ getChanContents respChan >>= mapM_ (send' . encodeMessage)
 	linkVar <- liftIO $ newMVar $ return ()
@@ -300,7 +300,7 @@ 		answer :: SessionMonad m => Msg (Message Response) -> m ()
 		answer m = do
 			unless (isNotification $ view (msg . message) m) $
-				Log.log Log.Trace $ " << {}" ~~ ellipsis (fromUtf8 (encode $ view (msg . message) m))
+				Log.log Log.Trace $ "responsed << {}" ~~ ellipsis (fromUtf8 (encode $ view (msg . message) m))
 			liftIO $ writeChan respChan m
 			where
 				ellipsis :: String -> String
@@ -309,21 +309,21 @@ 					| otherwise = take 100 str ++ "..."
 	-- flip finally (disconnected linkVar) $ forever $ Log.scopeLog (commandLogger copts) (T.pack name) $ do
 	reqs <- liftIO $ F.readChan rchan
-	flip finally (disconnected linkVar) $ Log.scope (T.pack name) $
+	flip finally (disconnected linkVar) $
 		forM_ reqs $ \req' -> do
-			Log.log Log.Trace $ " => {}" ~~ fromUtf8 req'
+			Log.log Log.Trace $ "received >> {}" ~~ fromUtf8 req'
 			case decodeMessage req' of
 				Left em -> do
 					Log.log Log.Warning $ "Invalid request {}" ~~ fromUtf8 req'
 					answer $ set msg (Message Nothing $ responseError $ RequestError "invalid request" $ fromUtf8 req') em
-				Right m -> void $ liftIO $ forkIO $ withSession s $
+				Right m -> void $ liftIO $ forkIO $ withSession s $ Log.scope (T.pack name) $ Log.scope "req" $
 					Log.scope (T.pack $ fromMaybe "_" (view (msg . messageId) m)) $ do
 						resp' <- flip (traverseOf (msg . message)) m $ \(Request c cdir noFile tm silent) -> do
 							let
 								onNotify n
 									| silent = return ()
 									| otherwise = traverseOf (msg . message) (const $ mmap' noFile (Response $ Left n)) m >>= answer
-							Log.log Log.Trace $ "{} >> {}" ~~ name ~~ fromUtf8 (encode c)
+							Log.log Log.Trace $ "requested >> {}" ~~ fromUtf8 (encode c)
 							resp <- liftIO $ fmap (Response . Right) $ handleTimeout tm $ hsdevLiftIO $ withSession s $
 								processRequest
 									CommandOptions {
@@ -352,17 +352,17 @@ 		-- Call on disconnected, either no action or exit command
 		disconnected :: SessionMonad m => MVar (IO ()) -> m ()
 		disconnected var = do
-			Log.log Log.Info $ "{} disconnected" ~~ name
+			Log.log Log.Info "disconnected"
 			liftIO $ join $ takeMVar var
 
 -- | Process client by socket
-processClientSocket :: SessionMonad m => Socket -> m ()
-processClientSocket s = do
+processClientSocket :: SessionMonad m => String -> Socket -> m ()
+processClientSocket name s = do
 	recvChan <- liftIO F.newChan
 	liftIO $ void $ forkIO $ finally
 		(Net.getContents s >>= mapM_ (F.putChan recvChan) . L.lines)
 		(F.closeChan recvChan)
-	processClient (show s) recvChan (sendLine s)
+	processClient name recvChan (sendLine s)
 	where
 		-- NOTE: Network version of `sendAll` goes to infinite loop on client socket close
 		-- when server's send is blocked, see https://github.com/haskell/network/issues/155
src/HsDev/Stack.hs view
@@ -38,6 +38,7 @@ import qualified HsDev.Tools.Ghc.Compat as Compat
 import HsDev.Scan.Browse (withPackages)
 import HsDev.Util as Util
+import HsDev.Tools.Base (runTool_)
 
 -- | Get compiler version
 stackCompiler :: MonadLog m => m String
@@ -64,7 +65,7 @@ 	stackExe <- Util.withCurrentDirectory (takeDirectory curExe) $
 		liftIO (findExecutable "stack") >>= maybe (hsdevError $ ToolNotFound "stack") return
 	comp <- stackCompiler
-	liftIO $ readProcess stackExe (cmd' ++ ["--compiler", comp, "--arch", stackArch]) ""
+	liftIO $ runTool_ stackExe (["--compiler", comp, "--arch", stackArch] ++ cmd')
 
 -- | Make yaml opts
 yaml :: Maybe FilePath -> [String]
src/HsDev/Symbols.hs view
@@ -58,7 +58,7 @@ import HsDev.Symbols.Types
 import HsDev.Symbols.Class
 import HsDev.Symbols.Documented (Documented(..))
-import HsDev.Util (searchPath, uniqueBy)
+import HsDev.Util (searchPath, uniqueBy, directoryContents)
 
 -- | Get name of export
 export :: Export -> Text
@@ -204,10 +204,10 @@ 	if isDir then locateHere file' else locateParent (takeDirectory file')
 	where
 		locateHere path = do
-			cts <- filter (not . null . takeBaseName) <$> getDirectoryContents path
+			cts <- filter (not . null . takeBaseName) <$> directoryContents path
 			return $ fmap (project . (path </>)) $ find ((== ".cabal") . takeExtension) cts
 		locateParent dir = do
-			cts <- filter (not . null . takeBaseName) <$> getDirectoryContents dir
+			cts <- filter (not . null . takeBaseName) <$> directoryContents dir
 			case find ((== ".cabal") . takeExtension) cts of
 				Nothing -> if isDrive dir then return Nothing else locateParent (takeDirectory dir)
 				Just cabalf -> return $ Just $ project (dir </> cabalf)
src/HsDev/Tools/Base.hs view
@@ -1,4 +1,5 @@ module HsDev.Tools.Base (
+	runTool, runTool_,
 	Result, ToolM,
 	runWait, runWait_,
 	tool, tool_,
@@ -27,6 +28,24 @@ import HsDev.Tools.Types
 import HsDev.Symbols
 import HsDev.Util (liftIOErrors)
+
+-- | Run tool, throwing HsDevError on fail
+runTool :: FilePath -> [String] -> String -> IO String
+runTool name args input = hsdevLiftIOWith onIOError $ do
+	(code, out, err) <- readProcessWithExitCode name args input
+	case code of
+		ExitFailure ecode -> hsdevError $ ToolError name $
+			"exited with code " ++ show ecode ++ ": " ++ err
+		ExitSuccess -> return out
+	where
+		onIOError s = ToolError name $ unlines [
+			"args: [" ++ intercalate ", " args ++ "]",
+			"stdin: " ++ input,
+			"error: " ++ s]
+
+-- | Run tool with not stdin
+runTool_ :: FilePath -> [String] -> IO String
+runTool_ name args = runTool name args ""
 
 type Result = Either String String
 type ToolM a = ExceptT String IO a
src/HsDev/Tools/ClearImports.hs view
@@ -74,7 +74,7 @@ -- | Clean temporary files
 cleanTmpImports :: FilePath -> IO ()
 cleanTmpImports dir = do
-	dumps <- liftM (map (dir </>) . filter ((== ".imports") . takeExtension)) $ getDirectoryContents dir
+	dumps <- liftM (filter ((== ".imports") . takeExtension)) $ directoryContents dir
 	forM_ dumps $ handle ignoreIO' . retry 1000 . removeFile
 	where
 		ignoreIO' :: IOException -> IO ()
src/HsDev/Tools/Ghc/Check.hs view
@@ -27,6 +27,7 @@ import HsDev.PackageDb
 import HsDev.Symbols.Location
 import HsDev.Symbols.Types
+import HsDev.Project (fileTarget)
 import HsDev.Tools.Base
 import HsDev.Tools.Ghc.Worker
 import HsDev.Tools.Ghc.Compat
src/HsDev/Tools/Ghc/MGhc.hs view
@@ -144,9 +144,6 @@ initSession = do
 	lib <- ask
 	initGhcMonad lib
-	fs <- getSessionDynFlags
-	void $ setSessionDynFlags fs
-	void $ liftIO $ initPackages fs
 	void saveSession
 
 activateSession :: (MonadIO m, ExceptionMonad m, Ord s) => s -> MGhcT s m (Maybe HscEnv)
src/HsDev/Tools/HDocs.hs view
@@ -19,11 +19,11 @@ import Data.Map (Map)
 import qualified Data.Map as M
 import Data.String (fromString)
-import System.Process (readProcess)
 
 import qualified HDocs.Module as HDocs
 import qualified HDocs.Haddock as HDocs (readSource, readSources_)
 
+import HsDev.Tools.Base
 import HsDev.Symbols
 
 -- | Get docs for modules
@@ -57,7 +57,5 @@ 	return $ setDocs d m
 
 hdocsProcess :: String -> [String] -> IO (Maybe (Map String String))
-hdocsProcess mname opts = handle onErr $ liftM (decode . L.pack . last . lines) $ readProcess "hdocs" opts' "" where
+hdocsProcess mname opts = liftM (decode . L.pack . last . lines) $ runTool_ "hdocs" opts' where
 	opts' = mname : concat [["-g", opt] | opt <- opts]
-	onErr :: SomeException -> IO (Maybe a)
-	onErr _ = return Nothing
src/HsDev/Util.hs view
@@ -70,11 +70,12 @@ withCurrentDirectory cur act = C.bracket (liftIO Dir.getCurrentDirectory) (liftIO . Dir.setCurrentDirectory) $
 	const (liftIO (Dir.setCurrentDirectory cur) >> act)
 
--- | Get directory contents safely
+-- | Get directory contents safely: no fail, ignoring symbolic links, also prepends paths with dir name
 directoryContents :: FilePath -> IO [FilePath]
 directoryContents p = handle ignore $ do
 	b <- Dir.doesDirectoryExist p
-	if b
+	isLink <- Dir.isSymbolicLink p
+	if b && (not isLink)
 		then liftM (map (p </>) . filter (`notElem` [".", ".."])) (Dir.getDirectoryContents p)
 		else return []
 	where
@@ -108,7 +109,9 @@ -- | Is one path parent of another
 isParent :: FilePath -> FilePath -> Bool
 isParent dir file = norm dir `isPrefixOf` norm file where
-	norm = splitDirectories . normalise
+	norm = dropDot . splitDirectories . normalise
+	dropDot ("." : chs) = chs
+	dropDot chs = chs
 
 -- | Is haskell source?
 haskellSource :: FilePath -> Bool
tests/Test.hs view
@@ -29,15 +29,19 @@ main :: IO ()
 main = hspec $ do
 	describe "scan project" $ do
+		dir <- runIO getCurrentDirectory
+		s <- runIO $ startServer (def { serverSilent = True })
 		it "should scan project" $ do
-			dir <- getCurrentDirectory
-			s <- startServer (def { serverSilent = True })
 			_ <- call s $ Scan [dir </> "tests/test-package"] False [] [] [] [] False False
 			one <- call s $ InfoResolve (dir </> "tests/test-package/ModuleOne.hs") True
-			when (["test", "forkIO", "f"] /= exports one) $
+			when (["test", "forkIO", "untypedFoo"] /= exports one) $
 				expectationFailure "invalid exports of ModuleOne.hs"
 			two <- call s $ InfoResolve (dir </> "tests/test-package/ModuleTwo.hs") True
-			when (["f", "twice"] /= exports two) $
+			when (["untypedFoo", "twice", "overloadedStrings"] /= exports two) $
 				expectationFailure "invalid exports of ModuleTwo.hs"
-			_ <- call s Exit
-			return ()
+		it "should pass extensions when checkings" $ do
+			checks <- call s (Check [FileSource (dir </> "tests/test-package/ModuleTwo.hs") Nothing] [])
+			when (("error" :: String) `elem` (checks ^.. traverseArray . key "level" . _Just)) $
+				expectationFailure "there should be no errors, only warnings"
+		_ <- runIO $ call s Exit
+		return ()
tests/test-package/ModuleOne.hs view
@@ -1,7 +1,7 @@ module ModuleOne (
 	test,
 	forkIO,
-	f
+	untypedFoo
 	) where
 
 import Control.Concurrent (forkIO)
@@ -11,4 +11,4 @@ test = return ()
 
 -- | Some function without type
-f x y = x + y
+untypedFoo x y = x + y
tests/test-package/ModuleTwo.hs view
@@ -1,10 +1,21 @@ module ModuleTwo (
-	f,
-	twice
+	untypedFoo,
+	twice,
+	overloadedStrings
 	) where
 
-import ModuleOne (f)
+import ModuleOne (untypedFoo)
 
+import Data.String
+
 -- | Apply function twice
 twice :: (a -> a) -> a -> a
 twice f = f . f
+
+data MyString = MyString String
+
+instance IsString MyString where
+	fromString = MyString
+
+overloadedStrings :: MyString
+overloadedStrings = "Hello"
tests/test-package/test-package.cabal view
@@ -3,7 +3,6 @@ synopsis:            hsdev test package
 -- description:         
 -- license:             
-license-file:        LICENSE
 author:              voidex
 maintainer:          voidex@live.com
 -- copyright:           
@@ -18,6 +17,8 @@     ModuleTwo
   -- other-modules:       
   -- other-extensions:    
-  build-depends:       base >=4.8 && <4.9
+  build-depends:       base
   hs-source-dirs:      .
   default-language:    Haskell2010
+  ghc-options: -Wall -fno-warn-tabs
+  default-extensions: OverloadedStrings