{-# LANGUAGE PackageImports #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE BangPatterns #-}
module Language.PureScript.Ide.State
( getLoadedModulenames
, getExternFiles
, getFileState
, resetIdeState
, cacheRebuild
, cachedRebuild
, insertExterns
, insertModule
, insertExternsSTM
, getAllModules
, populateVolatileState
, populateVolatileStateSync
, populateVolatileStateSTM
, resolveOperatorsForModule
, resolveInstances
, resolveDataConstructorsForModule
) where
import Protolude hiding (moduleName)
import Control.Arrow
import Control.Concurrent.STM
import Control.Lens hiding (op, (&))
import "monad-logger" Control.Monad.Logger
import qualified Data.Map.Lazy as Map
import qualified Language.PureScript as P
import Language.PureScript.Docs.Convert.Single (convertComments)
import Language.PureScript.Externs
import Language.PureScript.Ide.Externs
import Language.PureScript.Ide.Reexports
import Language.PureScript.Ide.SourceFile
import Language.PureScript.Ide.Types
import Language.PureScript.Ide.Util
resetIdeState :: Ide m => m ()
resetIdeState = do
ideVar <- ideStateVar <$> ask
liftIO (atomically (writeTVar ideVar emptyIdeState))
getLoadedModulenames :: Ide m => m [P.ModuleName]
getLoadedModulenames = Map.keys <$> getExternFiles
getExternFiles :: Ide m => m (ModuleMap ExternsFile)
getExternFiles = fsExterns <$> getFileState
insertModule :: Ide m => (FilePath, P.Module) -> m ()
insertModule module' = do
stateVar <- ideStateVar <$> ask
liftIO . atomically $ insertModuleSTM stateVar module'
insertModuleSTM :: TVar IdeState -> (FilePath, P.Module) -> STM ()
insertModuleSTM ref (fp, module') =
modifyTVar ref $ \x ->
x { ideFileState = (ideFileState x) {
fsModules = Map.insert
(P.getModuleName module')
(module', fp)
(fsModules (ideFileState x))}}
getFileState :: Ide m => m IdeFileState
getFileState = do
st <- ideStateVar <$> ask
ideFileState <$> liftIO (readTVarIO st)
getFileStateSTM :: TVar IdeState -> STM IdeFileState
getFileStateSTM ref = ideFileState <$> readTVar ref
getVolatileState :: Ide m => m IdeVolatileState
getVolatileState = do
st <- ideStateVar <$> ask
liftIO (atomically (getVolatileStateSTM st))
getVolatileStateSTM :: TVar IdeState -> STM IdeVolatileState
getVolatileStateSTM st = ideVolatileState <$> readTVar st
setVolatileStateSTM :: TVar IdeState -> IdeVolatileState -> STM ()
setVolatileStateSTM ref vs = do
modifyTVar ref $ \x ->
x {ideVolatileState = vs}
pure ()
getAllModules :: Ide m => Maybe P.ModuleName -> m (ModuleMap [IdeDeclarationAnn])
getAllModules mmoduleName = do
declarations <- vsDeclarations <$> getVolatileState
rebuild <- cachedRebuild
case mmoduleName of
Nothing -> pure declarations
Just moduleName ->
case rebuild of
Just (cachedModulename, ef)
| cachedModulename == moduleName -> do
AstData asts <- vsAstData <$> getVolatileState
let
ast =
fromMaybe (Map.empty, Map.empty) (Map.lookup moduleName asts)
cachedModule =
resolveLocationsForModule ast (fst (convertExterns ef))
tmp =
Map.insert moduleName cachedModule declarations
resolved =
Map.adjust (resolveOperatorsForModule tmp) moduleName tmp
pure resolved
_ -> pure declarations
insertExterns :: Ide m => ExternsFile -> m ()
insertExterns ef = do
st <- ideStateVar <$> ask
liftIO (atomically (insertExternsSTM st ef))
insertExternsSTM :: TVar IdeState -> ExternsFile -> STM ()
insertExternsSTM ref ef =
modifyTVar ref $ \x ->
x { ideFileState = (ideFileState x) {
fsExterns = Map.insert (efModuleName ef) ef (fsExterns (ideFileState x))}}
cacheRebuild :: Ide m => ExternsFile -> m ()
cacheRebuild ef = do
st <- ideStateVar <$> ask
liftIO . atomically . modifyTVar st $ \x ->
x { ideVolatileState = (ideVolatileState x) {
vsCachedRebuild = Just (efModuleName ef, ef)}}
cachedRebuild :: Ide m => m (Maybe (P.ModuleName, ExternsFile))
cachedRebuild = vsCachedRebuild <$> getVolatileState
populateVolatileStateSync :: (Ide m, MonadLogger m) => m ()
populateVolatileStateSync = do
st <- ideStateVar <$> ask
let message duration = "Finished populating volatile state in: " <> displayTimeSpec duration
results <- logPerf message $ do
!r <- liftIO (atomically (populateVolatileStateSTM st))
pure r
void $ Map.traverseWithKey
(\mn -> logWarnN . prettyPrintReexportResult (const (P.runModuleName mn)))
(Map.filter reexportHasFailures results)
populateVolatileState :: (Ide m, MonadLogger m) => m (Async ())
populateVolatileState = do
env <- ask
let ll = confLogLevel (ideConfiguration env)
liftIO (async (runLogger ll (runReaderT populateVolatileStateSync env)))
populateVolatileStateSTM
:: TVar IdeState
-> STM (ModuleMap (ReexportResult [IdeDeclarationAnn]))
populateVolatileStateSTM ref = do
IdeFileState{fsExterns = externs, fsModules = modules} <- getFileStateSTM ref
rebuildCache <- vsCachedRebuild <$> getVolatileStateSTM ref
let asts = map (extractAstInformation . fst) modules
let (moduleDeclarations, reexportRefs) = (map fst &&& map snd) (Map.map convertExterns externs)
results =
moduleDeclarations
& map resolveDataConstructorsForModule
& resolveLocations asts
& resolveDocumentation (map fst modules)
& resolveInstances externs
& resolveOperators
& resolveReexports reexportRefs
setVolatileStateSTM ref (IdeVolatileState (AstData asts) (map reResolved results) rebuildCache)
pure (force results)
resolveLocations
:: ModuleMap (DefinitionSites P.SourceSpan, TypeAnnotations)
-> ModuleMap [IdeDeclarationAnn]
-> ModuleMap [IdeDeclarationAnn]
resolveLocations asts =
Map.mapWithKey (\mn decls ->
maybe decls (flip resolveLocationsForModule decls) (Map.lookup mn asts))
resolveLocationsForModule
:: (DefinitionSites P.SourceSpan, TypeAnnotations)
-> [IdeDeclarationAnn]
-> [IdeDeclarationAnn]
resolveLocationsForModule (defs, types) decls =
map convertDeclaration decls
where
convertDeclaration :: IdeDeclarationAnn -> IdeDeclarationAnn
convertDeclaration (IdeDeclarationAnn ann d) = convertDeclaration' annotateFunction annotateValue annotateType annotateKind d
where
annotateFunction x = IdeDeclarationAnn (ann { _annLocation = Map.lookup (IdeNamespaced IdeNSValue (P.runIdent x)) defs
, _annTypeAnnotation = Map.lookup x types
})
annotateValue x = IdeDeclarationAnn (ann {_annLocation = Map.lookup (IdeNamespaced IdeNSValue x) defs})
annotateType x = IdeDeclarationAnn (ann {_annLocation = Map.lookup (IdeNamespaced IdeNSType x) defs})
annotateKind x = IdeDeclarationAnn (ann {_annLocation = Map.lookup (IdeNamespaced IdeNSKind x) defs})
convertDeclaration'
:: (P.Ident -> IdeDeclaration -> IdeDeclarationAnn)
-> (Text -> IdeDeclaration -> IdeDeclarationAnn)
-> (Text -> IdeDeclaration -> IdeDeclarationAnn)
-> (Text -> IdeDeclaration -> IdeDeclarationAnn)
-> IdeDeclaration
-> IdeDeclarationAnn
convertDeclaration' annotateFunction annotateValue annotateType annotateKind d =
case d of
IdeDeclValue v ->
annotateFunction (v ^. ideValueIdent) d
IdeDeclType t ->
annotateType (t ^. ideTypeName . properNameT) d
IdeDeclTypeSynonym s ->
annotateType (s ^. ideSynonymName . properNameT) d
IdeDeclDataConstructor dtor ->
annotateValue (dtor ^. ideDtorName . properNameT) d
IdeDeclTypeClass tc ->
annotateType (tc ^. ideTCName . properNameT) d
IdeDeclValueOperator operator ->
annotateValue (operator ^. ideValueOpName . opNameT) d
IdeDeclTypeOperator operator ->
annotateType (operator ^. ideTypeOpName . opNameT) d
IdeDeclKind i ->
annotateKind (i ^. properNameT) d
resolveDocumentation
:: ModuleMap P.Module
-> ModuleMap [IdeDeclarationAnn]
-> ModuleMap [IdeDeclarationAnn]
resolveDocumentation modules =
Map.mapWithKey (\mn decls ->
maybe decls (flip resolveDocumentationForModule decls) (Map.lookup mn modules))
resolveDocumentationForModule
:: P.Module
-> [IdeDeclarationAnn]
-> [IdeDeclarationAnn]
resolveDocumentationForModule (P.Module _ _ _ sdecls _) decls = map convertDecl decls
where
comments :: Map P.Name [P.Comment]
comments = Map.fromListWith (flip (<>)) $ mapMaybe (\d ->
case name d of
Just name' -> Just (name', snd $ P.declSourceAnn d)
_ -> Nothing)
sdecls
name :: P.Declaration -> Maybe P.Name
name (P.TypeDeclaration d) = Just $ P.IdentName $ P.tydeclIdent d
name decl = P.declName decl
convertDecl :: IdeDeclarationAnn -> IdeDeclarationAnn
convertDecl (IdeDeclarationAnn ann d) =
convertDeclaration'
(annotateValue . P.IdentName)
(annotateValue . P.IdentName . P.Ident)
(annotateValue . P.TyName . P.ProperName)
(annotateValue . P.KiName . P.ProperName)
d
where
docs :: P.Name -> Text
docs ident = fromMaybe "" $ convertComments =<< Map.lookup ident comments
annotateValue ident = IdeDeclarationAnn (ann { _annDocumentation = Just $ docs ident })
resolveInstances
:: ModuleMap P.ExternsFile
-> ModuleMap [IdeDeclarationAnn]
-> ModuleMap [IdeDeclarationAnn]
resolveInstances externs declarations =
Map.foldr (flip (foldr go)) declarations
. Map.mapWithKey (\mn ef -> mapMaybe (extractInstances mn) (efDeclarations ef))
$ externs
where
extractInstances mn P.EDInstance{..} =
case edInstanceClassName of
P.Qualified (Just classModule) className ->
Just (IdeInstance mn
edInstanceName
edInstanceTypes
edInstanceConstraints, classModule, className)
_ -> Nothing
extractInstances _ _ = Nothing
go
:: (IdeInstance, P.ModuleName, P.ProperName 'P.ClassName)
-> ModuleMap [IdeDeclarationAnn]
-> ModuleMap [IdeDeclarationAnn]
go (ideInstance, classModule, className) acc' =
let
matchTC =
anyOf (idaDeclaration . _IdeDeclTypeClass . ideTCName) (== className)
updateDeclaration =
mapIf matchTC (idaDeclaration
. _IdeDeclTypeClass
. ideTCInstances
%~ cons ideInstance)
in
acc' & ix classModule %~ updateDeclaration
resolveOperators
:: ModuleMap [IdeDeclarationAnn]
-> ModuleMap [IdeDeclarationAnn]
resolveOperators modules =
map (resolveOperatorsForModule modules) modules
resolveOperatorsForModule
:: ModuleMap [IdeDeclarationAnn]
-> [IdeDeclarationAnn]
-> [IdeDeclarationAnn]
resolveOperatorsForModule modules = map (idaDeclaration %~ resolveOperator)
where
getDeclarations :: P.ModuleName -> [IdeDeclaration]
getDeclarations moduleName =
Map.lookup moduleName modules
& fromMaybe []
& map discardAnn
resolveOperator (IdeDeclValueOperator op)
| (P.Qualified (Just mn) (Left ident)) <- op ^. ideValueOpAlias =
let t = getDeclarations mn
& mapMaybe (preview _IdeDeclValue)
& filter (anyOf ideValueIdent (== ident))
& map (view ideValueType)
& listToMaybe
in IdeDeclValueOperator (op & ideValueOpType .~ t)
| (P.Qualified (Just mn) (Right dtor)) <- op ^. ideValueOpAlias =
let t = getDeclarations mn
& mapMaybe (preview _IdeDeclDataConstructor)
& filter (anyOf ideDtorName (== dtor))
& map (view ideDtorType)
& listToMaybe
in IdeDeclValueOperator (op & ideValueOpType .~ t)
resolveOperator (IdeDeclTypeOperator op)
| P.Qualified (Just mn) properName <- op ^. ideTypeOpAlias =
let k = getDeclarations mn
& mapMaybe (preview _IdeDeclType)
& filter (anyOf ideTypeName (== properName))
& map (view ideTypeKind)
& listToMaybe
in IdeDeclTypeOperator (op & ideTypeOpKind .~ k)
resolveOperator x = x
mapIf :: Functor f => (b -> Bool) -> (b -> b) -> f b -> f b
mapIf p f = map (\x -> if p x then f x else x)
resolveDataConstructorsForModule
:: [IdeDeclarationAnn]
-> [IdeDeclarationAnn]
resolveDataConstructorsForModule decls =
map (idaDeclaration %~ resolveDataConstructors) decls
where
resolveDataConstructors :: IdeDeclaration -> IdeDeclaration
resolveDataConstructors decl = case decl of
IdeDeclType ty ->
IdeDeclType (ty & ideTypeDtors .~ fromMaybe [] (Map.lookup (ty^.ideTypeName) dtors))
_ ->
decl
dtors =
decls
& mapMaybe (preview (idaDeclaration._IdeDeclDataConstructor))
& foldr (\(IdeDataConstructor name typeName type') ->
Map.insertWith (<>) typeName [(name, type')]) Map.empty