psc-ide-0.5.0: lib/PureScript/Ide.hs
{-# LANGUAGE TupleSections #-}
module PureScript.Ide where
import Control.Monad.Except
import Control.Monad.State.Lazy (StateT (..), get, modify)
import Control.Monad.Trans.Either
import qualified Data.Map.Lazy as M
import Data.Maybe (mapMaybe, catMaybes)
import Data.Monoid
import qualified Data.Text as T
import PureScript.Ide.Completion
import PureScript.Ide.Externs
import PureScript.Ide.Pursuit
import PureScript.Ide.Error
import PureScript.Ide.Types
import PureScript.Ide.SourceFile
import PureScript.Ide.Reexports
import System.FilePath
import System.Directory
type PscIde = StateT PscState IO
getPscIdeState :: PscIde (M.Map ModuleIdent [ExternDecl])
getPscIdeState = pscStateModules <$> get
getAllDecls :: PscIde [ExternDecl]
getAllDecls = concat <$> getPscIdeState
getAllModules :: PscIde [Module]
getAllModules = M.toList <$> getPscIdeState
getAllModulesWithReexports :: PscIde [Module]
getAllModulesWithReexports = do
mis <- M.keys <$> getPscIdeState
ms <- traverse getModuleWithReexports mis
return (catMaybes ms)
getModule :: ModuleIdent -> PscIde (Maybe Module)
getModule m = do
modules <- getPscIdeState
return ((m,) <$> M.lookup m modules)
getModuleWithReexports :: ModuleIdent -> PscIde (Maybe Module)
getModuleWithReexports mi = do
m <- getModule mi
db <- getPscIdeState
case m of
Just m' -> do
resolved <- resolveReexports m' db
return (Just resolved)
Nothing -> return Nothing
where
resolveReexports m db = do
let replaced = replaceReexports m db
-- Maybe add logging for statements like these
-- liftIO $ print (getReexports replaced)
if null . getReexports $ replaced
then return replaced
else resolveReexports replaced db
insertModule :: Module -> PscIde ()
insertModule (name, decls) =
modify (\x -> x { pscStateModules = M.insert name decls (pscStateModules x)})
findCompletions :: [Filter] -> Matcher -> PscIde Success
findCompletions filters matcher =
CompletionResult <$> getCompletions filters matcher <$> getAllModulesWithReexports
findType :: DeclIdent -> [Filter] -> PscIde Success
findType search filters =
CompletionResult <$> getExactMatches search filters <$> getAllModulesWithReexports
findPursuitCompletions :: PursuitQuery -> PscIde Success
findPursuitCompletions (PursuitQuery q) =
PursuitResult <$> liftIO (searchPursuitForDeclarations q)
findPursuitPackages :: PursuitQuery -> PscIde Success
findPursuitPackages (PursuitQuery q) =
PursuitResult <$> liftIO (findPackagesForModuleIdent q)
loadExtern :: FilePath -> PscIde (Either Error ())
loadExtern fp = runEitherT $ do
m <- EitherT . liftIO $ readExternFile fp
lift (insertModule m)
printModules :: PscIde Success
printModules = do
modules <- M.keys <$> getPscIdeState
return (ModuleList modules)
listAvailableModules :: PscIde Success
listAvailableModules = liftIO $ do
cwd <- getCurrentDirectory
modules <- getDirectoryContents (cwd </> "output")
let cleanedModules = filter (`notElem` [".", ".."]) modules
return (ModuleList (map T.pack cleanedModules))
importsForFile :: FilePath -> PscIde (Either Error Success)
importsForFile fp = do
imports <- liftIO (getImportsForFile fp)
return (ImportList <$> imports)
-- | The first argument is a set of modules to load. The second argument
-- denotes modules for which to load dependencies
loadModulesAndDeps :: [ModuleIdent] -> [ModuleIdent] -> PscIde (Either Error Success)
loadModulesAndDeps mods deps = do
r1 <- mapM loadModule (mods ++ deps)
r2 <- mapM loadModuleDependencies deps
return $ do
moduleResults <- fmap T.concat (sequence r1)
dependencyResults <- fmap T.concat (sequence r2)
return (TextResult (moduleResults <> ", " <> dependencyResults))
loadModuleDependencies :: ModuleIdent -> PscIde (Either Error T.Text)
loadModuleDependencies moduleName = do
m <- getModule moduleName
case getDependenciesForModule <$> m of
Just deps -> do
mapM_ loadModule deps
-- We need to load the modules, that get reexported from the dependencies
depModules <- catMaybes <$> mapM getModule deps
-- What to do with errors here? This basically means a reexported dependency
-- doesn't exist in the output/ folder
_ <- traverse loadReexports depModules
return (Right ("Dependencies for " <> moduleName <> " loaded."))
Nothing -> return (Left (ModuleNotFound moduleName))
loadReexports :: Module -> PscIde (Either Error [ModuleIdent])
loadReexports m = case getReexports m of
[] -> return (Right [])
exportDeps -> runEitherT $ do
-- I'm fine with this crashing on a failed pattern match.
-- If this ever fails I'll need to look at GADTs
let reexports = map (\(Export mn) -> mn) exportDeps
-- liftIO $ print reexports
_ <- traverse (EitherT . loadModule) reexports
exportDepsModules <- lift $ catMaybes <$> traverse getModule reexports
exportDepDeps <- traverse (EitherT . loadReexports) exportDepsModules
return $ concat exportDepDeps
getDependenciesForModule :: Module -> [ModuleIdent]
getDependenciesForModule (_, decls) = mapMaybe getDependencyName decls
where getDependencyName (Dependency dependencyName _) = Just dependencyName
getDependencyName _ = Nothing
loadModule :: ModuleIdent -> PscIde (Either Error T.Text)
loadModule mn = runEitherT $ do
path <- EitherT . liftIO $ filePathFromModule "json" mn
EitherT (loadExtern path)
return ("Loaded extern file at: " <> T.pack path)
filePathFromModule :: String -> ModuleIdent -> IO (Either Error FilePath)
filePathFromModule extension moduleName = do
cwd <- getCurrentDirectory
let path = cwd </> "output" </> T.unpack moduleName </> "externs." ++ extension
ex <- doesFileExist path
return $
if ex
then Right path
else Left (ModuleFileNotFound moduleName)
-- | Taken from Data.Either.Utils
maybeToEither :: MonadError e m =>
e -- ^ (Left e) will be returned if the Maybe value is Nothing
-> Maybe a -- ^ (Right a) will be returned if this is (Just a)
-> m a
maybeToEither errorval Nothing = throwError errorval
maybeToEither _ (Just normalval) = return normalval