git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Cache last rebuild (#2083)

* Cache the last rebuilt module inside psc-ide state It's cached with its exports opened up, so that we can source completions from private members when we are editing a module. * Adds documentation to the functions inside ...Ide.State and breaks the functions into a pure and a stateful part to minimize time spent in STM * Cleans up json instances for Command to use Applicative style where possible * Don't add a default import for Prim rebuildModule inside ...Make already does that now * only parse a single module when rebuilding Now that multiple modules per file are forbidden, we can simplify the parsing code in Rebuild a bit. * remove the cacheSuccess flag * fix docs

commit57e9201ce59dfd1674370bfa73346870f7270615 authorChristoph Hegemann <kRITZCREEK@users.noreply.github.com> dateSun 22 May 2016, 20:22 (10 years ago) parentca98825
13 files changed+361−188
psc-ide-server/PROTOCOL.md+22−14 src/Language/PureScript/Ide.hs+15−12 src/Language/PureScript/Ide/Command.hs+56−58 src/Language/PureScript/Ide/Rebuild.hs+110−26 src/Language/PureScript/Ide/State.hs+97−53 src/Language/PureScript/Ide/Types.hs+4−3 src/Language/PureScript/Ide/Util.hs+3 src/Language/PureScript/Ide/Watcher.hs+7−6 src/Language/PureScript/Parser/Declarations.hs+16−5 tests/Language/PureScript/Ide/Integration.hs+17−9 tests/Language/PureScript/Ide/RebuildSpec.hs+7−1 tests/Language/PureScript/IdeSpec.hs+1−1 tests/support/pscide/src/RebuildSpecWithHiddenIdent.purs+6
psc-ide-server/PROTOCOL.md+22−14
@@ -38,15 +38,17 @@ The `type` command looks up the type for a given identifier.
3838
3939**Params:**
4040 - `search :: String`: The identifier to look for. Only matches on equality.
41− - `filters :: [Filter]`: These filters will be applied before looking for the
41+ - `filters :: (optional) [Filter]`: These filters will be applied before looking for the
4242 identifier. These filters get combined with *AND*, so a candidate must match *ALL*
4343 of them to be eligible.
44+ - `currentModule :: (optional) String`: see *Complete* command
4445```json
4546{
4647 "command": "type",
4748 "params": {
4849 "search": "filterM",
49− "filters": [Filter]
50+ "filters": [{..}],
51+ "currentModule": "Main"
5052 }
5153}
5254```
@@ -58,20 +60,25 @@ The possible types are returned in the same format as completions
5860The `complete` command looks up possible completions/corrections.
5961
6062**Params**:
61− - `filters :: [Filter]`: The same as for the `type` command. A candidate must match
62− all filters.
63− - `matcher :: (optional) Matcher`: The strategy used for matching candidates after filtering.
64− Results are scored internally and will be returned in the descending order where
65− the nth element is better then the n+1-th.
66−
67− If no matcher is given every candidate, that passes the filters, is returned in no
68− particular order.
63+ - `filters :: [Filter]`: The same as for the `type` command. A candidate must
64+ match all filters.
65+ - `matcher :: (optional) Matcher`: The strategy used for matching candidates
66+ after filtering. Results are scored internally and will be returned in the
67+ descending order where the nth element is better then the n+1-th.
68+ - `currentModule :: (optional) String`: The current modules name. If it matches
69+ with the rebuild cache non-exported modules will also be completed. You can
70+ fill the rebuild cache by using the "Rebuild" command.
71+
72+ If no matcher is given every candidate, that passes the filters, is returned
73+ in no particular order.
74+
6975```json
7076{
7177 "command": "complete",
7278 "params": {
73− "filters": [Filter],
74− "matcher": (optional) Matcher
79+ "filters": [{..}, {..}],
80+ "matcher": {..}
81+ "currentModule": "Main"
7582 }
7683}
7784```
@@ -244,10 +251,11 @@ Example:
244251
245252The `rebuild` command provides a fast rebuild for a single module. It doesn't
246253recompile the entire project though. All the modules dependencies need to be
247−loaded.
254+loaded. A successful rebuild will be stored to allow for completions of private
255+identifiers.
248256
249257Arguments:
250−- `file :: String` the path to the module to rebuild
258+ - `file :: String` the path to the module to rebuild
251259
252260```json
253261{
src/Language/PureScript/Ide.hs+15−12
@@ -36,6 +36,7 @@ import Data.Maybe (catMaybes, mapMaybe)
3636import Data.Monoid
3737import Data.Text (Text)
3838import qualified Data.Text as T
39+import qualified Language.PureScript as P
3940import qualified Language.PureScript.Ide.CaseSplit as CS
4041import Language.PureScript.Ide.Command
4142import Language.PureScript.Ide.Completion
@@ -60,10 +61,10 @@ handleCommand :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
6061handleCommand (Load [] []) = loadAllModules
6162handleCommand (Load modules deps) =
6263 loadModulesAndDeps modules deps
63−handleCommand (Type search filters) =
64− findType search filters
65−handleCommand (Complete filters matcher) =
66− findCompletions filters matcher
64+handleCommand (Type search filters currentModule) =
65+ findType search filters currentModule
66+handleCommand (Complete filters matcher currentModule) =
67+ findCompletions filters matcher currentModule
6768handleCommand (Pursuit query Package) =
6869 findPursuitPackages query
6970handleCommand (Pursuit query Identifier) =
@@ -94,14 +95,16 @@ handleCommand Reset = resetPscIdeState *> pure (TextResult "State has been reset
9495handleCommand Quit = liftIO exitSuccess
9596
9697findCompletions :: (PscIde m, MonadLogger m) =>
97− [Filter] -> Matcher -> m Success
98−findCompletions filters matcher =
99− CompletionResult . mapMaybe completionFromMatch . getCompletions filters matcher <$> getAllModulesWithReexports
98+ [Filter] -> Matcher -> Maybe P.ModuleName -> m Success
99+findCompletions filters matcher currentModule = do
100+ modules <- getAllModulesWithReexportsAndCache currentModule
101+ pure . CompletionResult . mapMaybe completionFromMatch . getCompletions filters matcher $ modules
100102
101103findType :: (PscIde m, MonadLogger m) =>
102− DeclIdent -> [Filter] -> m Success
103−findType search filters =
104− CompletionResult . mapMaybe completionFromMatch . getExactMatches search filters <$> getAllModulesWithReexports
104+ DeclIdent -> [Filter] -> Maybe P.ModuleName -> m Success
105+findType search filters currentModule = do
106+ modules <- getAllModulesWithReexportsAndCache currentModule
107+ pure . CompletionResult . mapMaybe completionFromMatch . getExactMatches search filters $ modules
105108
106109findPursuitCompletions :: (MonadIO m, MonadLogger m) =>
107110 PursuitQuery -> m Success
@@ -113,14 +116,14 @@ findPursuitPackages :: (MonadIO m, MonadLogger m) =>
113116findPursuitPackages (PursuitQuery q) =
114117 PursuitResult <$> liftIO (findPackagesForModuleIdent q)
115118
116−loadExtern ::(PscIde m, MonadLogger m, MonadError PscIdeError m) =>
119+loadExtern :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
117120 FilePath -> m ()
118121loadExtern fp = do
119122 m <- readExternFile fp
120123 insertModule m
121124
122125printModules :: (PscIde m) => m Success
123−printModules = printModules' <$> getPscIdeState
126+printModules = printModules' . pscIdeStateModules <$> getPscIdeState
124127
125128printModules' :: M.Map ModuleIdent [ExternDecl] -> Success
126129printModules' = ModuleList . M.keys
src/Language/PureScript/Ide/Command.hs+56−58
@@ -21,10 +21,8 @@ import Prelude.Compat
2121
2222import Control.Monad
2323import Data.Aeson
24−import Data.Maybe
2524import Data.Text (Text)
26−import Language.PureScript (ModuleName,
27− moduleNameFromString)
25+import qualified Language.PureScript as P
2826import Language.PureScript.Ide.CaseSplit
2927import Language.PureScript.Ide.Filter
3028import Language.PureScript.Ide.Matcher
@@ -36,12 +34,14 @@ data Command
3634 , loadDependencies :: [ModuleIdent]
3735 }
3836 | Type
39− { typeSearch :: DeclIdent
40− , typeFilters :: [Filter]
37+ { typeSearch :: DeclIdent
38+ , typeFilters :: [Filter]
39+ , typeCurrentModule :: Maybe P.ModuleName
4140 }
4241 | Complete
43− { completeFilters :: [Filter]
44− , completeMatcher :: Matcher
42+ { completeFilters :: [Filter]
43+ , completeMatcher :: Matcher
44+ , completeCurrentModule :: Maybe P.ModuleName
4545 }
4646 | Pursuit
4747 { pursuitQuery :: PursuitQuery
@@ -67,7 +67,7 @@ data Command
6767 | Quit
6868
6969data ImportCommand
70− = AddImplicitImport ModuleName
70+ = AddImplicitImport P.ModuleName
7171 | AddImportForIdentifier DeclIdent
7272 deriving (Show, Eq)
7373
@@ -75,12 +75,10 @@ instance FromJSON ImportCommand where
7575 parseJSON = withObject "ImportCommand" $ \o -> do
7676 (command :: String) <- o .: "importCommand"
7777 case command of
78− "addImplicitImport" -> do
79− mn <- o .: "module"
80− pure (AddImplicitImport (moduleNameFromString mn))
81− "addImport" -> do
82− ident <- o .: "identifier"
83− pure (AddImportForIdentifier ident)
78+ "addImplicitImport" ->
79+ AddImplicitImport <$> (P.moduleNameFromString <$> o .: "module")
80+ "addImport" ->
81+ AddImportForIdentifier <$> o .: "identifier"
8482 _ -> mzero
8583
8684data ListType = LoadedModules | Imports FilePath | AvailableModules
@@ -89,69 +87,69 @@ instance FromJSON ListType where
8987 parseJSON = withObject "ListType" $ \o -> do
9088 (listType' :: String) <- o .: "type"
9189 case listType' of
92− "import" -> do
93− fp <- o .: "file"
94− return (Imports fp)
95− "loadedModules" -> return LoadedModules
96− "availableModules" -> return AvailableModules
90+ "import" -> Imports <$> o .: "file"
91+ "loadedModules" -> pure LoadedModules
92+ "availableModules" -> pure AvailableModules
9793 _ -> mzero
9894
9995instance FromJSON Command where
10096 parseJSON = withObject "command" $ \o -> do
10197 (command :: String) <- o .: "command"
10298 case command of
103− "list" -> do
104− listType' <- o .:? "params"
105− return $ List (fromMaybe LoadedModules listType')
106− "cwd" -> return Cwd
107− "quit" -> return Quit
99+ "list" -> List <$> o .:? "params" .!= LoadedModules
100+ "cwd" -> pure Cwd
101+ "quit" -> pure Quit
108102 "reset" -> pure Reset
109− "load" ->
110− maybe (pure (Load [] [])) (\params -> do
111− mods <- params .:? "modules"
112− deps <- params .:? "dependencies"
113− pure $ Load (fromMaybe [] mods) (fromMaybe [] deps)) =<< o .:? "params"
103+ "load" -> do
104+ params' <- o .:? "params"
105+ case params' of
106+ Nothing -> pure (Load [] [])
107+ Just params ->
108+ Load
109+ <$> params .:? "modules" .!= []
110+ <*> params .:? "dependencies" .!= []
114111 "type" -> do
115112 params <- o .: "params"
116− search <- params .: "search"
117− filters <- params .: "filters"
118− return $ Type search filters
113+ Type
114+ <$> params .: "search"
115+ <*> params .: "filters"
116+ <*> (fmap P.moduleNameFromString <$> params .:? "currentModule")
119117 "complete" -> do
120118 params <- o .: "params"
121− filters <- params .:? "filters"
122− matcher <- params .:? "matcher"
123− return $ Complete (fromMaybe [] filters) (fromMaybe mempty matcher)
119+ Complete
120+ <$> params .:? "filters" .!= []
121+ <*> params .:? "matcher" .!= mempty
122+ <*> (fmap P.moduleNameFromString <$> params .:? "currentModule")
124123 "pursuit" -> do
125124 params <- o .: "params"
126− query <- params .: "query"
127− queryType <- params .: "type"
128− return $ Pursuit query queryType
125+ Pursuit
126+ <$> params .: "query"
127+ <*> params .: "type"
129128 "caseSplit" -> do
130129 params <- o .: "params"
131− line <- params .: "line"
132− begin <- params .: "begin"
133− end <- params .: "end"
134− annotations <- params .: "annotations"
135− type' <- params .: "type"
136− return $ CaseSplit line begin end (if annotations
137− then explicitAnnotations
138− else noAnnotations) type'
130+ CaseSplit
131+ <$> params .: "line"
132+ <*> params .: "begin"
133+ <*> params .: "end"
134+ <*> (mkAnnotations <$> params .: "annotations")
135+ <*> params .: "type"
139136 "addClause" -> do
140137 params <- o .: "params"
141− line <- params .: "line"
142− annotations <- params .: "annotations"
143− return $ AddClause line (if annotations
144− then explicitAnnotations
145− else noAnnotations)
138+ AddClause
139+ <$> params .: "line"
140+ <*> (mkAnnotations <$> params .: "annotations")
146141 "import" -> do
147142 params <- o .: "params"
148− fp <- params .: "file"
149− out <- params .:? "outfile"
150− filters <- params .:? "filters"
151− importCommand <- params .: "importCommand"
152− pure $ Import fp out (fromMaybe [] filters) importCommand
143+ Import
144+ <$> params .: "file"
145+ <*> params .:? "outfile"
146+ <*> params .:? "filters" .!= []
147+ <*> params .: "importCommand"
153148 "rebuild" -> do
154149 params <- o .: "params"
155− filePath <- params .: "file"
156− return $ Rebuild filePath
150+ Rebuild
151+ <$> params .: "file"
157152 _ -> mzero
153+ where
154+ mkAnnotations True = explicitAnnotations
155+ mkAnnotations False = noAnnotations
src/Language/PureScript/Ide/Rebuild.hs+110−26
@@ -1,43 +1,64 @@
11{-# LANGUAGE OverloadedStrings #-}
22{-# LANGUAGE PackageImports #-}
3+{-# LANGUAGE RecordWildCards #-}
4+{-# LANGUAGE TemplateHaskell #-}
5+{-# LANGUAGE TupleSections #-}
36
4−module Language.PureScript.Ide.Rebuild where
5−
6−import Prelude.Compat
7−
8−import Language.PureScript.Ide.Error
9−import Language.PureScript.Ide.State
10−import Language.PureScript.Ide.Types
7+module Language.PureScript.Ide.Rebuild
8+ ( rebuildFile
9+ ) where
1110
1211import Control.Monad.Error.Class
1312import Control.Monad.IO.Class
1413import "monad-logger" Control.Monad.Logger
15−import Control.Monad.Reader.Class
14+import Control.Monad.Reader
1615import Control.Monad.Trans.Except
1716import qualified Data.Map.Lazy as M
1817import Data.Maybe (fromJust, mapMaybe)
18+import Data.Monoid ((<>))
1919import qualified Data.Set as S
2020import qualified Language.PureScript as P
2121import Language.PureScript.Errors.JSON
22−import System.IO.UTF8 (readUTF8File)
22+import Language.PureScript.Ide.Error
23+import Language.PureScript.Ide.State
24+import Language.PureScript.Ide.Types
25+import Language.PureScript.Ide.Util
26+import Prelude.Compat
27+import System.IO.UTF8 (readUTF8File)
2328
29+-- | Given a filepath performs the following steps:
30+--
31+-- * Reads and parses a PureScript module from the filepath.
32+--
33+-- * Builds a dependency graph for the parsed module from the already loaded
34+-- ExternsFiles.
35+--
36+-- * Attempts to find an FFI definition file for the module by looking
37+-- for a file with the same filepath except for a .js extension.
38+--
39+-- * Passes all the created artifacts to @rebuildModule@.
40+--
41+-- * If the rebuilding succeeds, returns a @RebuildSuccess@ with the generated
42+-- warnings, and if rebuilding fails, returns a @RebuildError@ with the
43+-- generated errors.
2444rebuildFile
2545 :: (PscIde m, MonadLogger m, MonadError PscIdeError m)
2646 => FilePath
2747 -> m Success
2848rebuildFile path = do
2949
30− input <- liftIO $ readUTF8File path
50+ input <- liftIO (readUTF8File path)
3151
32− m <- case map snd <$> P.parseModulesFromFiles id [(path, input)] of
33− Left parseError ->
34− throwError . RebuildError . toJSONErrors False P.Error $ parseError
35− Right [m] -> pure m
36− Right _ -> throwError . GeneralError $ "Please define exactly one module."
52+ m <- case snd <$> P.parseModuleFromFile id (path, input) of
53+ Left parseError -> throwError
54+ . RebuildError
55+ . toJSONErrors False P.Error
56+ $ P.MultipleErrors [P.toPositionedError parseError]
57+ Right m -> pure m
3758
3859 -- Externs files must be sorted ahead of time, so that they get applied
3960 -- correctly to the 'Environment'.
40− externs <- sortExterns m . M.delete (P.getModuleName m) =<< getExternFiles
61+ externs <- sortExterns m =<< getExternFiles
4162
4263 outputDirectory <- confOutputPath . envConfiguration <$> ask
4364
@@ -46,26 +67,85 @@ rebuildFile path = do
4667 let filePathMap = M.singleton (P.getModuleName m) (Left P.RebuildAlways)
4768 foreigns <- P.inferForeignModules (M.singleton (P.getModuleName m) (Right path))
4869
49− -- Silence progress update messages during the build
50− let actions = (P.buildMakeActions outputDirectory filePathMap foreigns False)
51− { P.progress = const (pure ()) }
52−
70+ let makeEnv = MakeActionsEnv outputDirectory filePathMap foreigns False
5371 -- Rebuild the single module using the cached externs
5472 (result, warnings) <- liftIO
55− . P.runMake P.defaultOptions
56− . P.rebuildModule actions externs
57− $ m
73+ . P.runMake P.defaultOptions
74+ . P.rebuildModule (buildMakeActions
75+ >>= shushProgress $ makeEnv) externs $ m
5876 case result of
59− Left errors -> throwError . RebuildError $ toJSONErrors False P.Error errors
60− Right _ -> pure . RebuildSuccess $ toJSONErrors False P.Warning warnings
77+ Left errors -> throwError (RebuildError (toJSONErrors False P.Error errors))
78+ Right _ -> do
79+ rebuildModuleOpen makeEnv externs m
80+ pure (RebuildSuccess (toJSONErrors False P.Warning warnings))
81+
82+-- | Rebuilds a module but opens up its export list first and stores the result
83+-- inside the rebuild cache
84+rebuildModuleOpen
85+ :: (PscIde m, MonadLogger m, MonadError PscIdeError m)
86+ => MakeActionsEnv
87+ -> [P.ExternsFile]
88+ -> P.Module
89+ -> m ()
90+rebuildModuleOpen makeEnv externs m = do
91+ (openResult, _) <- liftIO
92+ . P.runMake P.defaultOptions
93+ . P.rebuildModule (buildMakeActions
94+ >>= shushProgress
95+ >>= shushCodegen
96+ $ makeEnv) externs $ openModuleExports m
97+ case openResult of
98+ Left _ ->
99+ throwError (GeneralError "Failed when rebuilding with open exports")
100+ Right result -> do
101+ $(logDebug)
102+ ("Setting Rebuild cache: " <> runModuleNameT (P.efModuleName result))
103+ setCachedRebuild result
104+
105+-- | Parameters we can access while building our @MakeActions@
106+data MakeActionsEnv =
107+ MakeActionsEnv
108+ { maeOutputDirectory :: FilePath
109+ , maeFilePathMap :: M.Map P.ModuleName (Either P.RebuildPolicy FilePath)
110+ , maeForeignPathMap :: M.Map P.ModuleName FilePath
111+ , maePrefixComment :: Bool
112+ }
61113
114+-- | Builds the default @MakeActions@ from a @MakeActionsEnv@
115+buildMakeActions :: MakeActionsEnv -> P.MakeActions P.Make
116+buildMakeActions MakeActionsEnv{..} =
117+ P.buildMakeActions
118+ maeOutputDirectory
119+ maeFilePathMap
120+ maeForeignPathMap
121+ maePrefixComment
122+
123+-- | Shuts the compiler up about progress messages
124+shushProgress :: P.MakeActions P.Make -> MakeActionsEnv -> P.MakeActions P.Make
125+shushProgress ma _ =
126+ ma { P.progress = \_ -> pure () }
127+
128+-- | Stops any kind of codegen (also silences errors about missing or unused FFI
129+-- files though)
130+shushCodegen :: P.MakeActions P.Make -> MakeActionsEnv -> P.MakeActions P.Make
131+shushCodegen ma MakeActionsEnv{..} =
132+ ma { P.codegen = \_ _ _ -> pure () }
133+
134+-- | Returns a topologically sorted list of dependent ExternsFiles for the given
135+-- module. Throws an error if there is a cyclic dependency within the
136+-- ExternsFiles
62137sortExterns
63138 :: (PscIde m, MonadError PscIdeError m)
64139 => P.Module
65140 -> M.Map P.ModuleName P.ExternsFile
66141 -> m [P.ExternsFile]
67142sortExterns m ex = do
68− sorted' <- runExceptT . P.sortModules . (:) m . map mkShallowModule . M.elems $ ex
143+ sorted' <- runExceptT
144+ . P.sortModules
145+ . (:) m
146+ . map mkShallowModule
147+ . M.elems
148+ . M.delete (P.getModuleName m) $ ex
69149 case sorted' of
70150 Left _ -> throwError (GeneralError "There was a cycle in the dependencies")
71151 Right (sorted, graph) -> do
@@ -80,3 +160,7 @@ sortExterns m ex = do
80160 -- Sort a list so its elements appear in the same order as in another list.
81161 inOrderOf :: (Ord a) => [a] -> [a] -> [a]
82162 inOrderOf xs ys = let s = S.fromList xs in filter (`S.member` s) ys
163+
164+-- | Removes a modules export list.
165+openModuleExports :: P.Module -> P.Module
166+openModuleExports (P.Module ss cs mn decls _) = P.Module ss cs mn decls Nothing
src/Language/PureScript/Ide/State.hs+97−53
@@ -16,7 +16,19 @@
1616{-# LANGUAGE PackageImports #-}
1717{-# LANGUAGE TemplateHaskell #-}
1818
19−module Language.PureScript.Ide.State where
19+module Language.PureScript.Ide.State
20+ ( getPscIdeState
21+ , getExternFiles
22+ , getModule
23+ , getModuleWithReexports
24+ , getAllModulesWithReexports
25+ , getAllModulesWithReexportsAndCache
26+ , insertModule
27+ , insertModuleSTM
28+ , getCachedRebuild
29+ , resetPscIdeState
30+ , setCachedRebuild
31+ ) where
2032
2133import Prelude ()
2234import Prelude.Compat
@@ -26,74 +38,106 @@ import Control.Monad.IO.Class
2638import "monad-logger" Control.Monad.Logger
2739import Control.Monad.Reader.Class
2840import qualified Data.Map.Lazy as M
29−import Data.Maybe (catMaybes)
41+import Data.Maybe (mapMaybe)
3042import Data.Monoid
31−import qualified Data.Text as T
3243import Language.PureScript.Externs
3344import Language.PureScript.Ide.Externs
3445import Language.PureScript.Ide.Reexports
3546import Language.PureScript.Ide.Types
36−import Language.PureScript.Names
47+import Language.PureScript.Ide.Util
48+import qualified Language.PureScript as P
3749
50+-- | Resets the PscIdeState to emptyPscIdeState
3851resetPscIdeState :: PscIde m => m ()
3952resetPscIdeState = do
4053 stateVar <- envStateVar <$> ask
4154 liftIO $ atomically (writeTVar stateVar emptyPscIdeState)
4255
43−getPscIdeState :: (PscIde m) =>
44− m (M.Map ModuleIdent [ExternDecl])
56+-- | Gets the entire PscIdeState
57+getPscIdeState :: PscIde m => m PscIdeState
4558getPscIdeState = do
4659 stateVar <- envStateVar <$> ask
47− liftIO $ pscStateModules <$> readTVarIO stateVar
60+ liftIO (readTVarIO stateVar)
4861
49−getExternFiles :: (PscIde m) =>
50− m (M.Map ModuleName ExternsFile)
62+-- | Gets all loaded ExternFiles
63+getExternFiles :: (PscIde m) => m (M.Map P.ModuleName ExternsFile)
5164getExternFiles = do
5265 stateVar <- envStateVar <$> ask
53− liftIO (externsFiles <$> readTVarIO stateVar)
54−
55−getExternFile :: (PscIde m) =>
56− ModuleName -> m (Maybe ExternsFile)
57−getExternFile mn = M.lookup mn <$> getExternFiles
58−
59−getAllDecls :: (PscIde m) => m [ExternDecl]
60−getAllDecls = concat <$> getPscIdeState
61−
62−getAllModules :: (PscIde m) => m [Module]
63−getAllModules = M.toList <$> getPscIdeState
64−
65−getAllModulesWithReexports :: (PscIde m, MonadLogger m) =>
66− m [Module]
67−getAllModulesWithReexports = do
68− mis <- M.keys <$> getPscIdeState
69− ms <- traverse getModuleWithReexports mis
70− pure (catMaybes ms)
71−
72−getModule :: (PscIde m, MonadLogger m) =>
73− ModuleIdent -> m (Maybe Module)
74−getModule m = do
75− modules <- getPscIdeState
76− pure ((m,) <$> M.lookup m modules)
77−
78−getModuleWithReexports :: (PscIde m, MonadLogger m) =>
79− ModuleIdent -> m (Maybe Module)
80−getModuleWithReexports mi = do
81− m <- getModule mi
82− modules <- getPscIdeState
83− pure $ resolveReexports modules <$> m
84−
85−insertModule ::(PscIde m, MonadLogger m) =>
66+ liftIO (pscIdeStateExternsFiles <$> readTVarIO stateVar)
67+
68+-- | Gets all loaded Modules and resolves Reexports
69+getAllModulesWithReexports :: (PscIde m) => m [Module]
70+getAllModulesWithReexports = getAllModulesWithReexports' <$> getPscIdeState
71+
72+-- | Pure version of @getAllModulesWithReexports@
73+getAllModulesWithReexports' :: PscIdeState -> [Module]
74+getAllModulesWithReexports' state =
75+ mapMaybe (getModuleWithReexports' state) (M.keys (pscIdeStateModules state))
76+
77+-- | Checks if the given ModuleName matches the last rebuild cache and if it
78+-- does, runs @getAllModulesWithReexports@ with the cached module replacing the
79+-- loaded module
80+getAllModulesWithReexportsAndCache
81+ :: (PscIde m)
82+ => Maybe P.ModuleName
83+ -> m [Module]
84+getAllModulesWithReexportsAndCache Nothing = getAllModulesWithReexports
85+getAllModulesWithReexportsAndCache (Just mn) = do
86+ state <- getPscIdeState
87+ cachedRebuild <- getCachedRebuild
88+ case cachedRebuild of
89+ Just (cachedIdent, ef) | cachedIdent == mn ->
90+ pure (getAllModulesWithReexports' (insertModule' ef state))
91+ _ -> getAllModulesWithReexports
92+
93+-- | Looks up a single Module inside the loaded Modules
94+getModule :: (PscIde m, MonadLogger m) => ModuleIdent -> m (Maybe Module)
95+getModule m = getModule' <$> getPscIdeState <*> pure m
96+
97+-- | Pure version of @getModule@
98+getModule' :: PscIdeState -> ModuleIdent -> Maybe Module
99+getModule' ps mi = (mi,) <$> M.lookup mi (pscIdeStateModules ps)
100+
101+-- | Looks up a single Module and resolves its Reexports
102+getModuleWithReexports :: PscIde m => ModuleIdent -> m (Maybe Module)
103+getModuleWithReexports i = getModuleWithReexports' <$> getPscIdeState <*> pure i
104+
105+-- | Pure version of @getModuleWithReexports@
106+getModuleWithReexports' :: PscIdeState -> ModuleIdent -> Maybe Module
107+getModuleWithReexports' ps mi =
108+ resolveReexports (pscIdeStateModules ps) <$> getModule' ps mi
109+
110+-- | Inserts an @ExternsFile@ into the PscIdeState. Also converts the
111+-- ExternsFile into psc-ide's internal Declaration format
112+insertModule :: (PscIde m, MonadLogger m) =>
86113 ExternsFile -> m ()
87114insertModule externsFile = do
88− env <- ask
115+ stateVar <- envStateVar <$> ask
89116 let moduleName = efModuleName externsFile
90− $(logDebug) $ "Inserting Module: " <> T.pack (runModuleName moduleName)
91− liftIO . atomically $ insertModule' (envStateVar env) externsFile
92−
93−insertModule' :: TVar PscIdeState -> ExternsFile -> STM ()
94−insertModule' st ef =
95− modifyTVar st $ \x ->
96− x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
97− , pscStateModules = let (mn, decls) = convertExterns ef
98− in M.insert mn decls (pscStateModules x)
99− }
117+ $(logDebug) $ "Inserting Module: " <> runModuleNameT moduleName
118+ liftIO . atomically $ insertModuleSTM stateVar externsFile
119+
120+-- | STM version of insertModule
121+insertModuleSTM :: TVar PscIdeState -> ExternsFile -> STM ()
122+insertModuleSTM st ef = modifyTVar st (insertModule' ef)
123+
124+-- | Pure version of insertModule
125+insertModule' :: ExternsFile -> PscIdeState -> PscIdeState
126+insertModule' ef state =
127+ state
128+ { pscIdeStateExternsFiles =
129+ M.insert (efModuleName ef) ef (pscIdeStateExternsFiles state)
130+ , pscIdeStateModules = let (mn, decls) = convertExterns ef
131+ in M.insert mn decls (pscIdeStateModules state)
132+ }
133+
134+-- | Sets rebuild cache to the given ExternsFile
135+setCachedRebuild :: PscIde m => ExternsFile -> m ()
136+setCachedRebuild ef = do
137+ st <- envStateVar <$> ask
138+ liftIO . atomically . modifyTVar st $ \x ->
139+ x { pscIdeStateCachedRebuild = Just (efModuleName ef, ef) }
140+
141+-- | Retrieves the rebuild cache
142+getCachedRebuild :: PscIde m => m (Maybe (P.ModuleName, ExternsFile))
143+getCachedRebuild = pscIdeStateCachedRebuild <$> getPscIdeState
src/Language/PureScript/Ide/Types.hs+4−3
@@ -83,12 +83,13 @@ type PscIde m = (MonadIO m, MonadReader PscIdeEnvironment m)
8383
8484data PscIdeState =
8585 PscIdeState
86− { pscStateModules :: M.Map Text [ExternDecl]
87− , externsFiles :: M.Map P.ModuleName ExternsFile
86+ { pscIdeStateModules :: M.Map Text [ExternDecl]
87+ , pscIdeStateExternsFiles :: M.Map P.ModuleName ExternsFile
88+ , pscIdeStateCachedRebuild :: Maybe (P.ModuleName, ExternsFile)
8889 } deriving Show
8990
9091emptyPscIdeState :: PscIdeState
91−emptyPscIdeState = PscIdeState M.empty M.empty
92+emptyPscIdeState = PscIdeState M.empty M.empty Nothing
9293
9394data Match = Match ModuleIdent ExternDecl
9495 deriving (Show, Eq)
src/Language/PureScript/Ide/Util.hs+3
@@ -34,6 +34,9 @@ runIdentT = T.pack . P.runIdent
3434runOpNameT :: P.OpName a -> Text
3535runOpNameT = T.pack . P.runOpName
3636
37+runModuleNameT :: P.ModuleName -> Text
38+runModuleNameT = T.pack . P.runModuleName
39+
3740prettyTypeT :: P.Type -> Text
3841prettyTypeT = T.unwords . fmap T.strip . T.lines . T.pack . P.prettyPrintType
3942
src/Language/PureScript/Ide/Watcher.hs+7−6
@@ -35,14 +35,15 @@ reloadFile stateVar ev = do
3535 case ef' of
3636 Left _ -> pure ()
3737 Right ef -> do
38− atomically (insertModule' stateVar ef)
38+ atomically (insertModuleSTM stateVar ef)
3939 putStrLn ("Reloaded File at: " ++ fp)
4040
4141-- | Installs filewatchers for the given directory and reloads ExternsFiles when
4242-- they change on disc
4343watcher :: TVar PscIdeState -> FilePath -> IO ()
44−watcher stateVar fp = withManagerConf (defaultConfig { confDebounce = NoDebounce }) $ \mgr -> do
45− _ <- watchTree mgr fp
46− (\ev -> takeFileName (eventPath ev) == "externs.json")
47− (reloadFile stateVar)
48− forever (threadDelay 100000)
44+watcher stateVar fp =
45+ withManagerConf (defaultConfig { confDebounce = NoDebounce }) $ \mgr -> do
46+ _ <- watchTree mgr fp
47+ (\ev -> takeFileName (eventPath ev) == "externs.json")
48+ (reloadFile stateVar)
49+ forever (threadDelay 100000)
src/Language/PureScript/Parser/Declarations.hs+16−5
@@ -5,12 +5,14 @@ module Language.PureScript.Parser.Declarations
55 ( parseDeclaration
66 , parseModule
77 , parseModulesFromFiles
8+ , parseModuleFromFile
89 , parseValue
910 , parseGuard
1011 , parseBinder
1112 , parseBinderNoParens
1213 , parseImportDeclaration'
1314 , parseLocalDeclaration
15+ , toPositionedError
1416 ) where
1517
1618import Prelude hiding (lex)
@@ -270,11 +272,7 @@ parseModulesFromFiles
270272 -> [(k, String)]
271273 -> m [(k, Module)]
272274parseModulesFromFiles toFilePath input =
273− flip parU wrapError . inParallel . flip map input $ \(k, content) -> do
274− let filename = toFilePath k
275− ts <- lex filename content
276− m <- runTokenParser filename parseModule ts
277− return (k, m)
275+ flip parU wrapError . inParallel . flip map input $ parseModuleFromFile toFilePath
278276 where
279277 wrapError :: Either P.ParseError a -> m a
280278 wrapError = either (throwError . MultipleErrors . pure . toPositionedError) return
@@ -285,6 +283,18 @@ parseModulesFromFiles toFilePath input =
285283 inParallel = withStrategy (parList rseq)
286284
287285
286+-- | Parses a single module with FilePath for eventual parsing errors
287+parseModuleFromFile
288+ :: (k -> FilePath)
289+ -> (k, String)
290+ -> Either P.ParseError (k, Module)
291+parseModuleFromFile toFilePath (k, content) = do
292+ let filename = toFilePath k
293+ ts <- lex filename content
294+ m <- runTokenParser filename parseModule ts
295+ pure (k, m)
296+
297+-- | Converts a @ParseError@ into a @PositionedError@
288298toPositionedError :: P.ParseError -> ErrorMessage
289299toPositionedError perr = ErrorMessage [ PositionedError (SourceSpan name start end) ] (ErrorParsingModule perr)
290300 where
@@ -562,3 +572,4 @@ parseBinderNoParens = P.choice
562572--
563573parseGuard :: TokenParser Guard
564574parseGuard = pipe *> C.indented *> parseValue
575+
tests/Language/PureScript/Ide/Integration.hs+17−9
@@ -33,6 +33,7 @@ module Language.PureScript.Ide.Integration
3333 , loadModuleWithDeps
3434 , getCwd
3535 , getFlexCompletions
36+ , getFlexCompletionsInModule
3637 , getType
3738 , rebuildModule
3839 , reset
@@ -49,7 +50,7 @@ import Data.Aeson
4950import Data.Aeson.Types
5051import qualified Data.ByteString.Lazy.UTF8 as BSL
5152import Data.Either (isRight)
52−import Data.Maybe (fromJust, isNothing)
53+import Data.Maybe (fromJust, isNothing, fromMaybe)
5354import qualified Data.Text as T
5455import qualified Data.Vector as V
5556import Language.PureScript.Ide.Util
@@ -95,11 +96,12 @@ compileTestProject :: IO Bool
9596compileTestProject = do
9697 pdir <- projectDirectory
9798 (_, _, _, procHandle) <- createProcess $
98− (shell $ "psc " ++ fileGlob) {cwd=Just pdir
99− ,std_out=CreatePipe
100− ,std_err=CreatePipe
99+ (shell $ "psc " ++ fileGlob) { cwd = Just pdir
100+ , std_out = CreatePipe
101+ , std_err = CreatePipe
101102 }
102− isSuccess <$> waitForProcess procHandle
103+ r <- tryNTimes 5 (getProcessExitCode procHandle)
104+ pure (fromMaybe False (isSuccess <$> r))
103105
104106tryNTimes :: Int -> IO (Maybe a) -> IO (Maybe a)
105107tryNTimes 0 _ = pure Nothing
@@ -166,7 +168,10 @@ loadModule :: String -> IO String
166168loadModule m = sendCommand $ load [m] []
167169
168170getFlexCompletions :: String -> IO [(String, String, String)]
169−getFlexCompletions q = parseCompletions <$> sendCommand (completion [] (Just (flexMatcher q)))
171+getFlexCompletions q = parseCompletions <$> sendCommand (completion [] (Just (flexMatcher q)) Nothing)
172+
173+getFlexCompletionsInModule :: String -> String -> IO [(String, String, String)]
174+getFlexCompletionsInModule q m = parseCompletions <$> sendCommand (completion [] (Just (flexMatcher q)) (Just m))
170175
171176getType :: String -> IO [(String, String, String)]
172177getType q = parseCompletions <$> sendCommand (typeC q [])
@@ -217,14 +222,17 @@ addImportW importCommand fp outfp =
217222 ])
218223
219224
220−completion :: [Value] -> Maybe Value -> Value
221−completion filters matcher =
225+completion :: [Value] -> Maybe Value -> Maybe String -> Value
226+completion filters matcher currentModule =
222227 let
223228 matcher' = case matcher of
224229 Nothing -> []
225230 Just m -> ["matcher" .= m]
231+ currentModule' = case currentModule of
232+ Nothing -> []
233+ Just cm -> ["currentModule" .= cm]
226234 in
227− commandWrapper "complete" (object $ "filters" .= filters : matcher')
235+ commandWrapper "complete" (object $ "filters" .= filters : matcher' ++ currentModule' )
228236
229237flexMatcher :: String -> Value
230238flexMatcher q = object [ "matcher" .= ("flex" :: String)
tests/Language/PureScript/Ide/RebuildSpec.hs+7−1
@@ -11,7 +11,7 @@ shouldBeFailure :: String -> IO ()
1111shouldBeFailure = shouldBe False . Integration.resultIsSuccess
1212
1313spec :: Spec
14−spec = before_ Integration.reset $ describe "Rebuilding single modules" $ do
14+spec = before_ Integration.reset . describe "Rebuilding single modules" $ do
1515 it "rebuilds a correct module without dependencies successfully" $ do
1616 _ <- Integration.loadModuleWithDeps "RebuildSpecSingleModule"
1717 pdir <- Integration.projectDirectory
@@ -45,3 +45,9 @@ spec = before_ Integration.reset $ describe "Rebuilding single modules" $ do
4545 pdir <- Integration.projectDirectory
4646 let file = pdir </> "src" </> "RebuildSpecWithMissingForeign.fail"
4747 Integration.rebuildModule file >>= shouldBeFailure
48+ it "completes a hidden identifier after rebuilding" $ do
49+ pdir <- Integration.projectDirectory
50+ let file = pdir </> "src" </> "RebuildSpecWithHiddenIdent.purs"
51+ Integration.rebuildModule file >>= shouldBeSuccess
52+ res <- Integration.getFlexCompletionsInModule "hid" "RebuildSpecWithHiddenIdent"
53+ shouldBe False (null res)
tests/Language/PureScript/IdeSpec.hs+1−1
@@ -11,7 +11,7 @@ import Language.PureScript.Ide.Types
1111import Test.Hspec
1212
1313testState :: PscIdeState
14−testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) Map.empty
14+testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) Map.empty Nothing
1515
1616defaultConfig :: Configuration
1717defaultConfig =
tests/support/pscide/src/RebuildSpecWithHiddenIdent.pursnew file+6
@@ -0,0 +1,6 @@
1+module RebuildSpecWithHiddenIdent (exported) where
2+
3+hidden x _ = x
4+
5+exported :: forall a. a -> a
6+exported x = x