Skip to content

Commit ee34e21

Browse files
committed
[psc-ide] Parse Modules on load
* psc-ide-server now takes globs as arguments, just like the compiler
1 parent 8063c4d commit ee34e21

File tree

10 files changed

+460
-166
lines changed

10 files changed

+460
-166
lines changed

LICENSE

Lines changed: 270 additions & 10 deletions
Large diffs are not rendered by default.

psc-ide-server/Main.hs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,6 @@ import System.IO.Error (isEOFError)
4444

4545
import qualified Paths_purescript as Paths
4646

47-
-- "Borrowed" from the Idris Compiler
48-
-- Copied from upstream impl of listenOn
49-
-- bound to localhost interface instead of iNADDR_ANY
5047
listenOnLocalhost :: PortNumber -> IO Socket
5148
listenOnLocalhost port = do
5249
proto <- getProtocolNumber "tcp"
@@ -62,6 +59,7 @@ listenOnLocalhost port = do
6259

6360
data Options = Options
6461
{ optionsDirectory :: Maybe FilePath
62+
, optionsGlobs :: [FilePath]
6563
, optionsOutputPath :: FilePath
6664
, optionsPort :: PortNumber
6765
, optionsNoWatch :: Bool
@@ -70,7 +68,7 @@ data Options = Options
7068

7169
main :: IO ()
7270
main = do
73-
Options dir outputPath port noWatch debug <- execParser opts
71+
Options dir globs outputPath port noWatch debug <- execParser opts
7472
maybe (pure ()) setCurrentDirectory dir
7573
serverState <- newTVarIO emptyPscIdeState
7674
ideState <- newTVarIO emptyIdeState
@@ -86,13 +84,14 @@ main = do
8684
unless noWatch $
8785
void (forkFinally (watcher ideState fullOutputPath) print)
8886

89-
let conf = Configuration {confDebug = debug, confOutputPath = outputPath}
87+
let conf = Configuration {confDebug = debug, confOutputPath = outputPath, confGlobs = globs}
9088
env = IdeEnvironment {envStateVar = serverState, ideStateVar = ideState, ideConfiguration = conf}
9189
startServer port env
9290
where
9391
parser =
9492
Options
9593
<$> optional (strOption (long "directory" `mappend` short 'd'))
94+
<*> many (argument str (metavar "Source GLOBS..."))
9695
<*> strOption (long "output-directory" `mappend` value "output/")
9796
<*> (fromIntegral <$>
9897
option auto (long "port" `mappend` short 'p' `mappend` value (4242 :: Integer)))

psc-ide-server/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ A tool which provides editor support for the PureScript programming language.
1010
* Vim integration is available here: https://github.com/FrigoEU/psc-ide-vim.
1111

1212
## Running the Server
13-
Start the server by running the `psc-ide-server` executable.
13+
14+
Start the server by running the `psc-ide-server [SOURCEGLOBS]` executable, where
15+
`SOURCEGLOBS` are (optional) globs that match your PureScript sourcefiles.
16+
1417
It supports the following options:
1518

1619
- `-p / --port` specify a port. Defaults to 4242

purescript.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ library
106106
boxes >= 0.1.4 && < 0.2.0,
107107
bytestring -any,
108108
containers -any,
109+
clock -any,
109110
directory >= 1.2,
110111
dlist -any,
111112
edit-distance -any,

src/Language/PureScript/Ide.hs

Lines changed: 103 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,10 @@
1414

1515
{-# LANGUAGE OverloadedStrings #-}
1616
{-# LANGUAGE PackageImports #-}
17+
{-# LANGUAGE TemplateHaskell #-}
1718

1819
module Language.PureScript.Ide
1920
( handleCommand
20-
-- for tests
21-
, printModules
2221
) where
2322

2423
import Protolude
@@ -41,67 +40,74 @@ import Language.PureScript.Ide.Types
4140
import Language.PureScript.Ide.Util
4241
import System.Directory
4342
import System.FilePath
43+
import System.FilePath.Glob
4444

4545
handleCommand :: (Ide m, MonadLogger m, MonadError PscIdeError m) =>
4646
Command -> m Success
47-
handleCommand (Load []) = loadAllModules
48-
handleCommand (Load modules) = loadModules modules
49-
handleCommand (Type search filters currentModule) =
50-
findType search filters currentModule
51-
handleCommand (Complete filters matcher currentModule) =
52-
findCompletions filters matcher currentModule
53-
handleCommand (Pursuit query Package) =
54-
findPursuitPackages query
55-
handleCommand (Pursuit query Identifier) =
56-
findPursuitCompletions query
57-
handleCommand (List LoadedModules) =
58-
printModules
59-
handleCommand (List AvailableModules) =
60-
listAvailableModules
61-
handleCommand (List (Imports fp)) =
62-
importsForFile fp
63-
handleCommand (CaseSplit l b e wca t) =
64-
caseSplit l b e wca t
65-
handleCommand (AddClause l wca) =
66-
addClause l wca
67-
handleCommand (Import fp outfp _ (AddImplicitImport mn)) = do
68-
rs <- addImplicitImport fp mn
69-
answerRequest outfp rs
70-
handleCommand (Import fp outfp filters (AddImportForIdentifier ident)) = do
71-
rs <- addImportForIdentifier fp ident filters
72-
case rs of
73-
Right rs' -> answerRequest outfp rs'
74-
Left question -> pure $ CompletionResult (map completionFromMatch question)
75-
handleCommand (Rebuild file) =
76-
rebuildFile file
77-
handleCommand Cwd =
78-
TextResult . toS <$> liftIO getCurrentDirectory
79-
handleCommand Reset = resetIdeState *> pure (TextResult "State has been reset.")
80-
handleCommand Quit = liftIO exitSuccess
81-
82-
findCompletions :: (Ide m) =>
47+
handleCommand c = case c of
48+
Load [] ->
49+
findAvailableExterns >>= loadModules
50+
Load modules ->
51+
loadModules modules
52+
Type search filters currentModule ->
53+
findType search filters currentModule
54+
Complete filters matcher currentModule ->
55+
findCompletions filters matcher currentModule
56+
Pursuit query Package ->
57+
findPursuitPackages query
58+
Pursuit query Identifier ->
59+
findPursuitCompletions query
60+
List LoadedModules ->
61+
printModules
62+
List AvailableModules ->
63+
listAvailableModules
64+
List (Imports fp) ->
65+
ImportList <$> getImportsForFile fp
66+
CaseSplit l b e wca t ->
67+
caseSplit l b e wca t
68+
AddClause l wca ->
69+
addClause l wca
70+
Import fp outfp _ (AddImplicitImport mn) -> do
71+
rs <- addImplicitImport fp mn
72+
answerRequest outfp rs
73+
Import fp outfp filters (AddImportForIdentifier ident) -> do
74+
rs <- addImportForIdentifier fp ident filters
75+
case rs of
76+
Right rs' -> answerRequest outfp rs'
77+
Left question ->
78+
pure (CompletionResult (map completionFromMatch question))
79+
Rebuild file ->
80+
rebuildFile file
81+
Cwd ->
82+
TextResult . toS <$> liftIO getCurrentDirectory
83+
Reset ->
84+
resetIdeState $> TextResult "State has been reset."
85+
Quit ->
86+
liftIO exitSuccess
87+
88+
findCompletions :: Ide m =>
8389
[Filter] -> Matcher -> Maybe P.ModuleName -> m Success
8490
findCompletions filters matcher currentModule = do
8591
modules <- getAllModules2 currentModule
8692
pure . CompletionResult . map completionFromMatch . getCompletions filters matcher $ modules
8793

88-
findType :: (Ide m) =>
94+
findType :: Ide m =>
8995
Text -> [Filter] -> Maybe P.ModuleName -> m Success
9096
findType search filters currentModule = do
9197
modules <- getAllModules2 currentModule
9298
pure . CompletionResult . map completionFromMatch . getExactMatches search filters $ modules
9399

94-
findPursuitCompletions :: (MonadIO m) =>
100+
findPursuitCompletions :: MonadIO m =>
95101
PursuitQuery -> m Success
96102
findPursuitCompletions (PursuitQuery q) =
97103
PursuitResult <$> liftIO (searchPursuitForDeclarations q)
98104

99-
findPursuitPackages :: (MonadIO m) =>
105+
findPursuitPackages :: MonadIO m =>
100106
PursuitQuery -> m Success
101107
findPursuitPackages (PursuitQuery q) =
102108
PursuitResult <$> liftIO (findPackagesForModuleIdent q)
103109

104-
printModules :: (Ide m) => m Success
110+
printModules :: Ide m => m Success
105111
printModules = ModuleList . map runModuleNameT <$> getLoadedModulenames
106112

107113
outputDirectory :: Ide m => m FilePath
@@ -131,54 +137,70 @@ addClause
131137
-> m Success
132138
addClause t wca = MultilineTextResult <$> CS.addClause t wca
133139

134-
importsForFile :: (MonadIO m, MonadError PscIdeError m) =>
135-
FilePath -> m Success
136-
importsForFile fp = do
137-
imports <- getImportsForFile fp
138-
pure (ImportList imports)
139-
140-
-- | Takes the output directory and a filepath like "Monad.Control.Eff" and
141-
-- looks up, whether that folder contains an externs.json
142-
checkExternsPath :: FilePath -> FilePath -> IO (Maybe FilePath)
143-
checkExternsPath oDir d
144-
| d `elem` [".", ".."] = pure Nothing
145-
| otherwise = do
146-
let file = oDir </> d </> "externs.json"
147-
ex <- doesFileExist file
148-
if ex
149-
then pure (Just file)
150-
else pure Nothing
151-
152-
findAllExterns :: (Ide m, MonadError PscIdeError m) => m [FilePath]
153-
findAllExterns = do
140+
-- | Finds all the externs.json files inside the output folder and returns the
141+
-- corresponding Modulenames
142+
findAvailableExterns :: (Ide m, MonadError PscIdeError m) => m [P.ModuleName]
143+
findAvailableExterns = do
154144
oDir <- outputDirectory
155145
unlessM (liftIO (doesDirectoryExist oDir))
156146
(throwError (GeneralError "Couldn't locate your output directory."))
157147
liftIO $ do
158-
dirs <- getDirectoryContents oDir
159-
externPaths <- traverse (checkExternsPath oDir) dirs
160-
pure (catMaybes externPaths)
161-
148+
directories <- getDirectoryContents oDir
149+
moduleNames <- filterM (checkExternsPath oDir) directories
150+
pure (P.moduleNameFromString <$> moduleNames)
151+
where
152+
-- | Takes the output directory and a filepath like "Monad.Control.Eff" and
153+
-- looks up, whether that folder contains an externs.json
154+
checkExternsPath :: FilePath -> FilePath -> IO Bool
155+
checkExternsPath oDir d
156+
| d `elem` [".", ".."] = pure False
157+
| otherwise = do
158+
let file = oDir </> d </> "externs.json"
159+
doesFileExist file
160+
161+
-- | Finds all matches for the globs specified at the commandline
162+
findAllSourceFiles :: Ide m => m [FilePath]
163+
findAllSourceFiles = do
164+
globs <- confGlobs . ideConfiguration <$> ask
165+
liftIO (concatMapM glob globs)
166+
167+
-- | Looks up the ExternsFiles for the given Modulenames and loads them into the
168+
-- server state. Then proceeds to parse all the specified sourcefiles and
169+
-- inserts their ASTs into the state. Finally kicks off an async worker, which
170+
-- populates Stage 2 and 3 of the state.
162171
loadModules
163172
:: (Ide m, MonadError PscIdeError m, MonadLogger m)
164173
=> [P.ModuleName]
165174
-> m Success
166-
loadModules mns = do
175+
loadModules moduleNames = do
176+
-- We resolve all the modulenames to externs files and load these into memory.
167177
oDir <- outputDirectory
168-
let efPaths = map (\mn -> oDir </> P.runModuleName mn </> "externs.json") mns
178+
let efPaths =
179+
map (\mn -> oDir </> P.runModuleName mn </> "externs.json") moduleNames
169180
efiles <- traverse readExternFile efPaths
170181
traverse_ insertExterns efiles
171-
--TODO Get rid of this once ModuleOld is gone
172-
traverse_ insertModule efiles
173-
populateStage2
174-
pure (TextResult ("Loaded " <> foldMap runModuleNameT mns <> "."))
175-
176-
loadAllModules :: (Ide m, MonadError PscIdeError m) => m Success
177-
loadAllModules = do
178-
exts <- traverse readExternFile =<< findAllExterns
179-
traverse_ insertExterns exts
180-
--TODO Get rid of this once ModuleOld is gone
181-
traverse_ insertModule exts
182+
183+
-- We parse all source files, log eventual parse failures if the debug flag
184+
-- was set and insert the succesful parses into the state.
185+
(failures, allModules) <-
186+
partitionEithers <$> (traverse parseModule =<< findAllSourceFiles)
187+
unless (null failures) $
188+
$(logDebug) ("Failed to parse: " <> show failures)
189+
traverse_ insertModule allModules
190+
191+
-- Because we still need the "old" module format to resolve reexports in the
192+
-- worker thread, we insert it into the state aswell.
193+
-- TODO Get rid of this once ModuleOld is gone
194+
traverse_ insertModuleOld efiles
195+
196+
-- Finally we kick off the worker with @async@ and return the number of
197+
-- successfully parsed modules.
182198
env <- ask
183-
_ <- liftIO $ async (runStdoutLoggingT (runReaderT populateStage2 env))
184-
pure (TextResult "All modules loaded.")
199+
let runLogger =
200+
runStdoutLoggingT
201+
. filterLogger (\_ _ -> confDebug (ideConfiguration env))
202+
-- populateStage2 returns Unit for now, so it's fine to discard this result.
203+
-- We might want to block on this in a benchmarking situation.
204+
_ <- liftIO (async (runLogger (runReaderT populateStage2 env)))
205+
pure (TextResult ("Loaded " <> show (length efiles) <> " modules and "
206+
<> show (length allModules) <> " source files."))

src/Language/PureScript/Ide/CaseSplit.hs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import Language.PureScript.Ide.Types
3636
import Language.PureScript.Ide.Util
3737

3838
import Text.Parsec as Parsec
39+
import qualified Text.PrettyPrint.Boxes as Box
3940

4041
type Constructor = (P.ProperName 'P.ConstructorName, [P.Type])
4142

@@ -135,8 +136,8 @@ parseTypeDeclaration' s =
135136
Right (P.TypeDeclaration i t) -> pure (i, t)
136137
Right _ -> throwError (GeneralError "Found a non-type-declaration")
137138
Left err ->
138-
throwError (GeneralError ("Parsing the typesignature failed with: "
139-
<> show err))
139+
throwError (GeneralError ("Parsing the type signature failed with: "
140+
<> toS (Box.render (P.prettyPrintParseError err))))
140141

141142
splitFunctionType :: P.Type -> [P.Type]
142143
splitFunctionType t = fromMaybe [] arguments

src/Language/PureScript/Ide/Reexports.hs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import Protolude
2828

2929
import Data.List (union)
3030
import qualified Data.Map as Map
31-
import qualified Data.Text as T
3231
import Language.PureScript.Ide.Types
3332
import Language.PureScript.Ide.Externs
3433
import qualified Language.PureScript as P
@@ -94,5 +93,5 @@ resolveReexports modules m =
9493
then replaced
9594
else resolveReexports modules replaced
9695

97-
resolveReexports2 :: Map T.Text [ExternDecl] -> ModuleOld -> Module
96+
resolveReexports2 :: Map Text [ExternDecl] -> ModuleOld -> Module
9897
resolveReexports2 decls = convertModule . resolveReexports decls

0 commit comments

Comments
 (0)