git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Cache last rebuild (#2083)
57e9201Christoph Hegemann
psc-ide-server/PROTOCOL.md+22−14
@@ -38,15 +38,17 @@ The `type` command looks up the type for a given identifier.
38
38
39**Params:**
39**Params:**
40 - `search :: String`: The identifier to look for. Only matches on equality.
40 - `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
42 identifier. These filters get combined with *AND*, so a candidate must match *ALL*
42 identifier. These filters get combined with *AND*, so a candidate must match *ALL*
43 of them to be eligible.
43 of them to be eligible.
44+ - `currentModule :: (optional) String`: see *Complete* command
44```json
45```json
45{
46{
46 "command": "type",
47 "command": "type",
47 "params": {
48 "params": {
48 "search": "filterM",
49 "search": "filterM",
49− "filters": [Filter]
50+ "filters": [{..}],
51+ "currentModule": "Main"
50 }
52 }
51}
53}
52```
54```
@@ -58,20 +60,25 @@ The possible types are returned in the same format as completions
58The `complete` command looks up possible completions/corrections.
60The `complete` command looks up possible completions/corrections.
59
61
60**Params**:
62**Params**:
61− - `filters :: [Filter]`: The same as for the `type` command. A candidate must match
63+ - `filters :: [Filter]`: The same as for the `type` command. A candidate must
62− all filters.
64+ match all filters.
63− - `matcher :: (optional) Matcher`: The strategy used for matching candidates after filtering.
65+ - `matcher :: (optional) Matcher`: The strategy used for matching candidates
64− Results are scored internally and will be returned in the descending order where
66+ after filtering. Results are scored internally and will be returned in the
65− the nth element is better then the n+1-th.
67+ descending order where the nth element is better then the n+1-th.
66−
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+
67− If no matcher is given every candidate, that passes the filters, is returned in no
72+ If no matcher is given every candidate, that passes the filters, is returned
68− particular order.
73+ in no particular order.
74+
69```json
75```json
70{
76{
71 "command": "complete",
77 "command": "complete",
72 "params": {
78 "params": {
73− "filters": [Filter],
79+ "filters": [{..}, {..}],
74− "matcher": (optional) Matcher
80+ "matcher": {..}
81+ "currentModule": "Main"
75 }
82 }
76}
83}
77```
84```
@@ -244,10 +251,11 @@ Example:
244
251
245The `rebuild` command provides a fast rebuild for a single module. It doesn't
252The `rebuild` command provides a fast rebuild for a single module. It doesn't
246recompile the entire project though. All the modules dependencies need to be
253recompile 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.
248
256
249Arguments:
257Arguments:
250−- `file :: String` the path to the module to rebuild
258+ - `file :: String` the path to the module to rebuild
251
259
252```json
260```json
253{
261{
src/Language/PureScript/Ide.hs+15−12
@@ -36,6 +36,7 @@ import Data.Maybe (catMaybes, mapMaybe)
36import Data.Monoid
36import Data.Monoid
37import Data.Text (Text)
37import Data.Text (Text)
38import qualified Data.Text as T
38import qualified Data.Text as T
39+import qualified Language.PureScript as P
39import qualified Language.PureScript.Ide.CaseSplit as CS
40import qualified Language.PureScript.Ide.CaseSplit as CS
40import Language.PureScript.Ide.Command
41import Language.PureScript.Ide.Command
41import Language.PureScript.Ide.Completion
42import Language.PureScript.Ide.Completion
@@ -60,10 +61,10 @@ handleCommand :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
60handleCommand (Load [] []) = loadAllModules
61handleCommand (Load [] []) = loadAllModules
61handleCommand (Load modules deps) =
62handleCommand (Load modules deps) =
62 loadModulesAndDeps modules deps
63 loadModulesAndDeps modules deps
63−handleCommand (Type search filters) =
64+handleCommand (Type search filters currentModule) =
64− findType search filters
65+ findType search filters currentModule
65−handleCommand (Complete filters matcher) =
66+handleCommand (Complete filters matcher currentModule) =
66− findCompletions filters matcher
67+ findCompletions filters matcher currentModule
67handleCommand (Pursuit query Package) =
68handleCommand (Pursuit query Package) =
68 findPursuitPackages query
69 findPursuitPackages query
69handleCommand (Pursuit query Identifier) =
70handleCommand (Pursuit query Identifier) =
@@ -94,14 +95,16 @@ handleCommand Reset = resetPscIdeState *> pure (TextResult "State has been reset
94handleCommand Quit = liftIO exitSuccess
95handleCommand Quit = liftIO exitSuccess
95
96
96findCompletions :: (PscIde m, MonadLogger m) =>
97findCompletions :: (PscIde m, MonadLogger m) =>
97− [Filter] -> Matcher -> m Success
98+ [Filter] -> Matcher -> Maybe P.ModuleName -> m Success
98−findCompletions filters matcher =
99+findCompletions filters matcher currentModule = do
100+ modules <- getAllModulesWithReexportsAndCache currentModule
99− CompletionResult . mapMaybe completionFromMatch . getCompletions filters matcher <$> getAllModulesWithReexports
101+ pure . CompletionResult . mapMaybe completionFromMatch . getCompletions filters matcher $ modules
100
102
101findType :: (PscIde m, MonadLogger m) =>
103findType :: (PscIde m, MonadLogger m) =>
102− DeclIdent -> [Filter] -> m Success
104+ DeclIdent -> [Filter] -> Maybe P.ModuleName -> m Success
103−findType search filters =
105+findType search filters currentModule = do
106+ modules <- getAllModulesWithReexportsAndCache currentModule
104− CompletionResult . mapMaybe completionFromMatch . getExactMatches search filters <$> getAllModulesWithReexports
107+ pure . CompletionResult . mapMaybe completionFromMatch . getExactMatches search filters $ modules
105
108
106findPursuitCompletions :: (MonadIO m, MonadLogger m) =>
109findPursuitCompletions :: (MonadIO m, MonadLogger m) =>
107 PursuitQuery -> m Success
110 PursuitQuery -> m Success
@@ -113,14 +116,14 @@ findPursuitPackages :: (MonadIO m, MonadLogger m) =>
113findPursuitPackages (PursuitQuery q) =
116findPursuitPackages (PursuitQuery q) =
114 PursuitResult <$> liftIO (findPackagesForModuleIdent q)
117 PursuitResult <$> liftIO (findPackagesForModuleIdent q)
115
118
116−loadExtern ::(PscIde m, MonadLogger m, MonadError PscIdeError m) =>
119+loadExtern :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
117 FilePath -> m ()
120 FilePath -> m ()
118loadExtern fp = do
121loadExtern fp = do
119 m <- readExternFile fp
122 m <- readExternFile fp
120 insertModule m
123 insertModule m
121
124
122printModules :: (PscIde m) => m Success
125printModules :: (PscIde m) => m Success
123−printModules = printModules' <$> getPscIdeState
126+printModules = printModules' . pscIdeStateModules <$> getPscIdeState
124
127
125printModules' :: M.Map ModuleIdent [ExternDecl] -> Success
128printModules' :: M.Map ModuleIdent [ExternDecl] -> Success
126printModules' = ModuleList . M.keys
129printModules' = ModuleList . M.keys
src/Language/PureScript/Ide/Command.hs+56−58
@@ -21,10 +21,8 @@ import Prelude.Compat
21
21
22import Control.Monad
22import Control.Monad
23import Data.Aeson
23import Data.Aeson
24−import Data.Maybe
25import Data.Text (Text)
24import Data.Text (Text)
26−import Language.PureScript (ModuleName,
25+import qualified Language.PureScript as P
27− moduleNameFromString)
28import Language.PureScript.Ide.CaseSplit
26import Language.PureScript.Ide.CaseSplit
29import Language.PureScript.Ide.Filter
27import Language.PureScript.Ide.Filter
30import Language.PureScript.Ide.Matcher
28import Language.PureScript.Ide.Matcher
@@ -36,12 +34,14 @@ data Command
36 , loadDependencies :: [ModuleIdent]
34 , loadDependencies :: [ModuleIdent]
37 }
35 }
38 | Type
36 | Type
39− { typeSearch :: DeclIdent
37+ { typeSearch :: DeclIdent
40− , typeFilters :: [Filter]
38+ , typeFilters :: [Filter]
39+ , typeCurrentModule :: Maybe P.ModuleName
41 }
40 }
42 | Complete
41 | Complete
43− { completeFilters :: [Filter]
42+ { completeFilters :: [Filter]
44− , completeMatcher :: Matcher
43+ , completeMatcher :: Matcher
44+ , completeCurrentModule :: Maybe P.ModuleName
45 }
45 }
46 | Pursuit
46 | Pursuit
47 { pursuitQuery :: PursuitQuery
47 { pursuitQuery :: PursuitQuery
@@ -67,7 +67,7 @@ data Command
67 | Quit
67 | Quit
68
68
69data ImportCommand
69data ImportCommand
70− = AddImplicitImport ModuleName
70+ = AddImplicitImport P.ModuleName
71 | AddImportForIdentifier DeclIdent
71 | AddImportForIdentifier DeclIdent
72 deriving (Show, Eq)
72 deriving (Show, Eq)
73
73
@@ -75,12 +75,10 @@ instance FromJSON ImportCommand where
75 parseJSON = withObject "ImportCommand" $ \o -> do
75 parseJSON = withObject "ImportCommand" $ \o -> do
76 (command :: String) <- o .: "importCommand"
76 (command :: String) <- o .: "importCommand"
77 case command of
77 case command of
78− "addImplicitImport" -> do
78+ "addImplicitImport" ->
79− mn <- o .: "module"
80− pure (AddImplicitImport (moduleNameFromString mn))
79+ AddImplicitImport <$> (P.moduleNameFromString <$> o .: "module")
81− "addImport" -> do
80+ "addImport" ->
82− ident <- o .: "identifier"
83− pure (AddImportForIdentifier ident)
81+ AddImportForIdentifier <$> o .: "identifier"
84 _ -> mzero
82 _ -> mzero
85
83
86data ListType = LoadedModules | Imports FilePath | AvailableModules
84data ListType = LoadedModules | Imports FilePath | AvailableModules
@@ -89,69 +87,69 @@ instance FromJSON ListType where
89 parseJSON = withObject "ListType" $ \o -> do
87 parseJSON = withObject "ListType" $ \o -> do
90 (listType' :: String) <- o .: "type"
88 (listType' :: String) <- o .: "type"
91 case listType' of
89 case listType' of
92− "import" -> do
90+ "import" -> Imports <$> o .: "file"
93− fp <- o .: "file"
94− return (Imports fp)
95− "loadedModules" -> return LoadedModules
91+ "loadedModules" -> pure LoadedModules
96− "availableModules" -> return AvailableModules
92+ "availableModules" -> pure AvailableModules
97 _ -> mzero
93 _ -> mzero
98
94
99instance FromJSON Command where
95instance FromJSON Command where
100 parseJSON = withObject "command" $ \o -> do
96 parseJSON = withObject "command" $ \o -> do
101 (command :: String) <- o .: "command"
97 (command :: String) <- o .: "command"
102 case command of
98 case command of
103− "list" -> do
99+ "list" -> List <$> o .:? "params" .!= LoadedModules
104− listType' <- o .:? "params"
105− return $ List (fromMaybe LoadedModules listType')
106− "cwd" -> return Cwd
100+ "cwd" -> pure Cwd
107− "quit" -> return Quit
101+ "quit" -> pure Quit
108 "reset" -> pure Reset
102 "reset" -> pure Reset
109− "load" ->
103+ "load" -> do
110− maybe (pure (Load [] [])) (\params -> do
104+ params' <- o .:? "params"
105+ case params' of
106+ Nothing -> pure (Load [] [])
107+ Just params ->
108+ Load
111− mods <- params .:? "modules"
109+ <$> params .:? "modules" .!= []
112− deps <- params .:? "dependencies"
110+ <*> params .:? "dependencies" .!= []
113− pure $ Load (fromMaybe [] mods) (fromMaybe [] deps)) =<< o .:? "params"
114 "type" -> do
111 "type" -> do
115 params <- o .: "params"
112 params <- o .: "params"
116− search <- params .: "search"
113+ Type
117− filters <- params .: "filters"
114+ <$> params .: "search"
118− return $ Type search filters
115+ <*> params .: "filters"
116+ <*> (fmap P.moduleNameFromString <$> params .:? "currentModule")
119 "complete" -> do
117 "complete" -> do
120 params <- o .: "params"
118 params <- o .: "params"
121− filters <- params .:? "filters"
119+ Complete
122− matcher <- params .:? "matcher"
120+ <$> params .:? "filters" .!= []
123− return $ Complete (fromMaybe [] filters) (fromMaybe mempty matcher)
121+ <*> params .:? "matcher" .!= mempty
122+ <*> (fmap P.moduleNameFromString <$> params .:? "currentModule")
124 "pursuit" -> do
123 "pursuit" -> do
125 params <- o .: "params"
124 params <- o .: "params"
125+ Pursuit
126− query <- params .: "query"
126+ <$> params .: "query"
127− queryType <- params .: "type"
127+ <*> params .: "type"
128− return $ Pursuit query queryType
129 "caseSplit" -> do
128 "caseSplit" -> do
130 params <- o .: "params"
129 params <- o .: "params"
131− line <- params .: "line"
130+ CaseSplit
132− begin <- params .: "begin"
131+ <$> params .: "line"
133− end <- params .: "end"
132+ <*> params .: "begin"
134− annotations <- params .: "annotations"
133+ <*> params .: "end"
135− type' <- params .: "type"
134+ <*> (mkAnnotations <$> params .: "annotations")
136− return $ CaseSplit line begin end (if annotations
135+ <*> params .: "type"
137− then explicitAnnotations
138− else noAnnotations) type'
139 "addClause" -> do
136 "addClause" -> do
140 params <- o .: "params"
137 params <- o .: "params"
141− line <- params .: "line"
142− annotations <- params .: "annotations"
143− return $ AddClause line (if annotations
138+ AddClause
144− then explicitAnnotations
139+ <$> params .: "line"
145− else noAnnotations)
140+ <*> (mkAnnotations <$> params .: "annotations")
146 "import" -> do
141 "import" -> do
147 params <- o .: "params"
142 params <- o .: "params"
143+ Import
148− fp <- params .: "file"
144+ <$> params .: "file"
149− out <- params .:? "outfile"
145+ <*> params .:? "outfile"
150− filters <- params .:? "filters"
146+ <*> params .:? "filters" .!= []
151− importCommand <- params .: "importCommand"
152− pure $ Import fp out (fromMaybe [] filters) importCommand
147+ <*> params .: "importCommand"
153 "rebuild" -> do
148 "rebuild" -> do
154 params <- o .: "params"
149 params <- o .: "params"
150+ Rebuild
155− filePath <- params .: "file"
151+ <$> params .: "file"
156− return $ Rebuild filePath
157 _ -> mzero
152 _ -> mzero
153+ where
154+ mkAnnotations True = explicitAnnotations
155+ mkAnnotations False = noAnnotations
src/Language/PureScript/Ide/Rebuild.hs+110−26
@@ -1,43 +1,64 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE PackageImports #-}
2{-# LANGUAGE PackageImports #-}
3+{-# LANGUAGE RecordWildCards #-}
4+{-# LANGUAGE TemplateHaskell #-}
5+{-# LANGUAGE TupleSections #-}
3
6
4−module Language.PureScript.Ide.Rebuild where
7+module Language.PureScript.Ide.Rebuild
5−
8+ ( rebuildFile
6−import Prelude.Compat
9+ ) where
7−
8−import Language.PureScript.Ide.Error
9−import Language.PureScript.Ide.State
10−import Language.PureScript.Ide.Types
11
10
12import Control.Monad.Error.Class
11import Control.Monad.Error.Class
13import Control.Monad.IO.Class
12import Control.Monad.IO.Class
14import "monad-logger" Control.Monad.Logger
13import "monad-logger" Control.Monad.Logger
15−import Control.Monad.Reader.Class
14+import Control.Monad.Reader
16import Control.Monad.Trans.Except
15import Control.Monad.Trans.Except
17import qualified Data.Map.Lazy as M
16import qualified Data.Map.Lazy as M
18import Data.Maybe (fromJust, mapMaybe)
17import Data.Maybe (fromJust, mapMaybe)
18+import Data.Monoid ((<>))
19import qualified Data.Set as S
19import qualified Data.Set as S
20import qualified Language.PureScript as P
20import qualified Language.PureScript as P
21import Language.PureScript.Errors.JSON
21import Language.PureScript.Errors.JSON
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
22−import System.IO.UTF8 (readUTF8File)
27+import System.IO.UTF8 (readUTF8File)
23
28
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.
24rebuildFile
44rebuildFile
25 :: (PscIde m, MonadLogger m, MonadError PscIdeError m)
45 :: (PscIde m, MonadLogger m, MonadError PscIdeError m)
26 => FilePath
46 => FilePath
27 -> m Success
47 -> m Success
28rebuildFile path = do
48rebuildFile path = do
29
49
30− input <- liftIO $ readUTF8File path
50+ input <- liftIO (readUTF8File path)
31
51
32− m <- case map snd <$> P.parseModulesFromFiles id [(path, input)] of
52+ m <- case snd <$> P.parseModuleFromFile id (path, input) of
33− Left parseError ->
53+ Left parseError -> throwError
54+ . RebuildError
34− throwError . RebuildError . toJSONErrors False P.Error $ parseError
55+ . toJSONErrors False P.Error
56+ $ P.MultipleErrors [P.toPositionedError parseError]
35− Right [m] -> pure m
57+ Right m -> pure m
36− Right _ -> throwError . GeneralError $ "Please define exactly one module."
37
58
38 -- Externs files must be sorted ahead of time, so that they get applied
59 -- Externs files must be sorted ahead of time, so that they get applied
39 -- correctly to the 'Environment'.
60 -- correctly to the 'Environment'.
40− externs <- sortExterns m . M.delete (P.getModuleName m) =<< getExternFiles
61+ externs <- sortExterns m =<< getExternFiles
41
62
42 outputDirectory <- confOutputPath . envConfiguration <$> ask
63 outputDirectory <- confOutputPath . envConfiguration <$> ask
43
64
@@ -46,26 +67,85 @@ rebuildFile path = do
46 let filePathMap = M.singleton (P.getModuleName m) (Left P.RebuildAlways)
67 let filePathMap = M.singleton (P.getModuleName m) (Left P.RebuildAlways)
47 foreigns <- P.inferForeignModules (M.singleton (P.getModuleName m) (Right path))
68 foreigns <- P.inferForeignModules (M.singleton (P.getModuleName m) (Right path))
48
69
49− -- Silence progress update messages during the build
50− let actions = (P.buildMakeActions outputDirectory filePathMap foreigns False)
70+ let makeEnv = MakeActionsEnv outputDirectory filePathMap foreigns False
51− { P.progress = const (pure ()) }
52−
53 -- Rebuild the single module using the cached externs
71 -- Rebuild the single module using the cached externs
54 (result, warnings) <- liftIO
72 (result, warnings) <- liftIO
55− . P.runMake P.defaultOptions
73+ . P.runMake P.defaultOptions
56− . P.rebuildModule actions externs
74+ . P.rebuildModule (buildMakeActions
57− $ m
75+ >>= shushProgress $ makeEnv) externs $ m
58 case result of
76 case result of
59− Left errors -> throwError . RebuildError $ toJSONErrors False P.Error errors
77+ Left errors -> throwError (RebuildError (toJSONErrors False P.Error errors))
78+ Right _ -> do
79+ rebuildModuleOpen makeEnv externs m
60− Right _ -> pure . RebuildSuccess $ toJSONErrors False P.Warning warnings
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+ }
61
113
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
62sortExterns
137sortExterns
63 :: (PscIde m, MonadError PscIdeError m)
138 :: (PscIde m, MonadError PscIdeError m)
64 => P.Module
139 => P.Module
65 -> M.Map P.ModuleName P.ExternsFile
140 -> M.Map P.ModuleName P.ExternsFile
66 -> m [P.ExternsFile]
141 -> m [P.ExternsFile]
67sortExterns m ex = do
142sortExterns m ex = do
143+ sorted' <- runExceptT
144+ . P.sortModules
145+ . (:) m
68− sorted' <- runExceptT . P.sortModules . (:) m . map mkShallowModule . M.elems $ ex
146+ . map mkShallowModule
147+ . M.elems
148+ . M.delete (P.getModuleName m) $ ex
69 case sorted' of
149 case sorted' of
70 Left _ -> throwError (GeneralError "There was a cycle in the dependencies")
150 Left _ -> throwError (GeneralError "There was a cycle in the dependencies")
71 Right (sorted, graph) -> do
151 Right (sorted, graph) -> do
@@ -80,3 +160,7 @@ sortExterns m ex = do
80 -- Sort a list so its elements appear in the same order as in another list.
160 -- Sort a list so its elements appear in the same order as in another list.
81 inOrderOf :: (Ord a) => [a] -> [a] -> [a]
161 inOrderOf :: (Ord a) => [a] -> [a] -> [a]
82 inOrderOf xs ys = let s = S.fromList xs in filter (`S.member` s) ys
162 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 @@
16{-# LANGUAGE PackageImports #-}
16{-# LANGUAGE PackageImports #-}
17{-# LANGUAGE TemplateHaskell #-}
17{-# LANGUAGE TemplateHaskell #-}
18
18
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
20
32
21import Prelude ()
33import Prelude ()
22import Prelude.Compat
34import Prelude.Compat
@@ -26,74 +38,106 @@ import Control.Monad.IO.Class
26import "monad-logger" Control.Monad.Logger
38import "monad-logger" Control.Monad.Logger
27import Control.Monad.Reader.Class
39import Control.Monad.Reader.Class
28import qualified Data.Map.Lazy as M
40import qualified Data.Map.Lazy as M
29−import Data.Maybe (catMaybes)
41+import Data.Maybe (mapMaybe)
30import Data.Monoid
42import Data.Monoid
31−import qualified Data.Text as T
32import Language.PureScript.Externs
43import Language.PureScript.Externs
33import Language.PureScript.Ide.Externs
44import Language.PureScript.Ide.Externs
34import Language.PureScript.Ide.Reexports
45import Language.PureScript.Ide.Reexports
35import Language.PureScript.Ide.Types
46import Language.PureScript.Ide.Types
36−import Language.PureScript.Names
47+import Language.PureScript.Ide.Util
48+import qualified Language.PureScript as P
37
49
50+-- | Resets the PscIdeState to emptyPscIdeState
38resetPscIdeState :: PscIde m => m ()
51resetPscIdeState :: PscIde m => m ()
39resetPscIdeState = do
52resetPscIdeState = do
40 stateVar <- envStateVar <$> ask
53 stateVar <- envStateVar <$> ask
41 liftIO $ atomically (writeTVar stateVar emptyPscIdeState)
54 liftIO $ atomically (writeTVar stateVar emptyPscIdeState)
42
55
56+-- | Gets the entire PscIdeState
43−getPscIdeState :: (PscIde m) =>
57+getPscIdeState :: PscIde m => m PscIdeState
44− m (M.Map ModuleIdent [ExternDecl])
45getPscIdeState = do
58getPscIdeState = do
46 stateVar <- envStateVar <$> ask
59 stateVar <- envStateVar <$> ask
47− liftIO $ pscStateModules <$> readTVarIO stateVar
60+ liftIO (readTVarIO stateVar)
48
61
49−getExternFiles :: (PscIde m) =>
62+-- | Gets all loaded ExternFiles
50− m (M.Map ModuleName ExternsFile)
63+getExternFiles :: (PscIde m) => m (M.Map P.ModuleName ExternsFile)
51getExternFiles = do
64getExternFiles = do
52 stateVar <- envStateVar <$> ask
65 stateVar <- envStateVar <$> ask
53− liftIO (externsFiles <$> readTVarIO stateVar)
66+ liftIO (pscIdeStateExternsFiles <$> readTVarIO stateVar)
54−
67+
55−getExternFile :: (PscIde m) =>
68+-- | Gets all loaded Modules and resolves Reexports
56− ModuleName -> m (Maybe ExternsFile)
69+getAllModulesWithReexports :: (PscIde m) => m [Module]
57−getExternFile mn = M.lookup mn <$> getExternFiles
70+getAllModulesWithReexports = getAllModulesWithReexports' <$> getPscIdeState
58−
71+
59−getAllDecls :: (PscIde m) => m [ExternDecl]
72+-- | Pure version of @getAllModulesWithReexports@
60−getAllDecls = concat <$> getPscIdeState
61−
62−getAllModules :: (PscIde m) => m [Module]
63−getAllModules = M.toList <$> getPscIdeState
64−
65−getAllModulesWithReexports :: (PscIde m, MonadLogger m) =>
73+getAllModulesWithReexports' :: PscIdeState -> [Module]
66− m [Module]
67−getAllModulesWithReexports = do
74+getAllModulesWithReexports' state =
68− mis <- M.keys <$> getPscIdeState
75+ mapMaybe (getModuleWithReexports' state) (M.keys (pscIdeStateModules state))
69− ms <- traverse getModuleWithReexports mis
76+
70− pure (catMaybes ms)
77+-- | Checks if the given ModuleName matches the last rebuild cache and if it
71−
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
72−getModule :: (PscIde m, MonadLogger m) =>
94+getModule :: (PscIde m, MonadLogger m) => ModuleIdent -> m (Maybe Module)
73− ModuleIdent -> m (Maybe Module)
74−getModule m = do
75− modules <- getPscIdeState
95+getModule m = getModule' <$> getPscIdeState <*> pure m
76− pure ((m,) <$> M.lookup m modules)
96+
77−
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
78−getModuleWithReexports :: (PscIde m, MonadLogger m) =>
102+getModuleWithReexports :: PscIde m => ModuleIdent -> m (Maybe Module)
79− ModuleIdent -> m (Maybe Module)
80−getModuleWithReexports mi = do
103+getModuleWithReexports i = getModuleWithReexports' <$> getPscIdeState <*> pure i
81− m <- getModule mi
104+
82− modules <- getPscIdeState
105+-- | Pure version of @getModuleWithReexports@
106+getModuleWithReexports' :: PscIdeState -> ModuleIdent -> Maybe Module
107+getModuleWithReexports' ps mi =
83− pure $ resolveReexports modules <$> m
108+ resolveReexports (pscIdeStateModules ps) <$> getModule' ps mi
84−
109+
110+-- | Inserts an @ExternsFile@ into the PscIdeState. Also converts the
111+-- ExternsFile into psc-ide's internal Declaration format
85−insertModule ::(PscIde m, MonadLogger m) =>
112+insertModule :: (PscIde m, MonadLogger m) =>
86 ExternsFile -> m ()
113 ExternsFile -> m ()
87insertModule externsFile = do
114insertModule externsFile = do
88− env <- ask
115+ stateVar <- envStateVar <$> ask
89 let moduleName = efModuleName externsFile
116 let moduleName = efModuleName externsFile
90− $(logDebug) $ "Inserting Module: " <> T.pack (runModuleName moduleName)
117+ $(logDebug) $ "Inserting Module: " <> runModuleNameT moduleName
91− liftIO . atomically $ insertModule' (envStateVar env) externsFile
118+ liftIO . atomically $ insertModuleSTM stateVar externsFile
92−
119+
120+-- | STM version of insertModule
93−insertModule' :: TVar PscIdeState -> ExternsFile -> STM ()
121+insertModuleSTM :: TVar PscIdeState -> ExternsFile -> STM ()
94−insertModule' st ef =
95− modifyTVar st $ \x ->
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 =
96− x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
129+ M.insert (efModuleName ef) ef (pscIdeStateExternsFiles state)
97− , pscStateModules = let (mn, decls) = convertExterns ef
130+ , pscIdeStateModules = let (mn, decls) = convertExterns ef
98− in M.insert mn decls (pscStateModules x)
131+ in M.insert mn decls (pscIdeStateModules state)
99− }
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)
83
83
84data PscIdeState =
84data PscIdeState =
85 PscIdeState
85 PscIdeState
86− { pscStateModules :: M.Map Text [ExternDecl]
86+ { pscIdeStateModules :: M.Map Text [ExternDecl]
87− , externsFiles :: M.Map P.ModuleName ExternsFile
87+ , pscIdeStateExternsFiles :: M.Map P.ModuleName ExternsFile
88+ , pscIdeStateCachedRebuild :: Maybe (P.ModuleName, ExternsFile)
88 } deriving Show
89 } deriving Show
89
90
90emptyPscIdeState :: PscIdeState
91emptyPscIdeState :: PscIdeState
91−emptyPscIdeState = PscIdeState M.empty M.empty
92+emptyPscIdeState = PscIdeState M.empty M.empty Nothing
92
93
93data Match = Match ModuleIdent ExternDecl
94data Match = Match ModuleIdent ExternDecl
94 deriving (Show, Eq)
95 deriving (Show, Eq)
src/Language/PureScript/Ide/Util.hs+3
@@ -34,6 +34,9 @@ runIdentT = T.pack . P.runIdent
34runOpNameT :: P.OpName a -> Text
34runOpNameT :: P.OpName a -> Text
35runOpNameT = T.pack . P.runOpName
35runOpNameT = T.pack . P.runOpName
36
36
37+runModuleNameT :: P.ModuleName -> Text
38+runModuleNameT = T.pack . P.runModuleName
39+
37prettyTypeT :: P.Type -> Text
40prettyTypeT :: P.Type -> Text
38prettyTypeT = T.unwords . fmap T.strip . T.lines . T.pack . P.prettyPrintType
41prettyTypeT = T.unwords . fmap T.strip . T.lines . T.pack . P.prettyPrintType
39
42
src/Language/PureScript/Ide/Watcher.hs+7−6
@@ -35,14 +35,15 @@ reloadFile stateVar ev = do
35 case ef' of
35 case ef' of
36 Left _ -> pure ()
36 Left _ -> pure ()
37 Right ef -> do
37 Right ef -> do
38− atomically (insertModule' stateVar ef)
38+ atomically (insertModuleSTM stateVar ef)
39 putStrLn ("Reloaded File at: " ++ fp)
39 putStrLn ("Reloaded File at: " ++ fp)
40
40
41-- | Installs filewatchers for the given directory and reloads ExternsFiles when
41-- | Installs filewatchers for the given directory and reloads ExternsFiles when
42-- they change on disc
42-- they change on disc
43watcher :: TVar PscIdeState -> FilePath -> IO ()
43watcher :: TVar PscIdeState -> FilePath -> IO ()
44+watcher stateVar fp =
44−watcher stateVar fp = withManagerConf (defaultConfig { confDebounce = NoDebounce }) $ \mgr -> do
45+ withManagerConf (defaultConfig { confDebounce = NoDebounce }) $ \mgr -> do
45− _ <- watchTree mgr fp
46+ _ <- watchTree mgr fp
46− (\ev -> takeFileName (eventPath ev) == "externs.json")
47+ (\ev -> takeFileName (eventPath ev) == "externs.json")
47− (reloadFile stateVar)
48+ (reloadFile stateVar)
48− forever (threadDelay 100000)
49+ forever (threadDelay 100000)
src/Language/PureScript/Parser/Declarations.hs+16−5
@@ -5,12 +5,14 @@ module Language.PureScript.Parser.Declarations
5 ( parseDeclaration
5 ( parseDeclaration
6 , parseModule
6 , parseModule
7 , parseModulesFromFiles
7 , parseModulesFromFiles
8+ , parseModuleFromFile
8 , parseValue
9 , parseValue
9 , parseGuard
10 , parseGuard
10 , parseBinder
11 , parseBinder
11 , parseBinderNoParens
12 , parseBinderNoParens
12 , parseImportDeclaration'
13 , parseImportDeclaration'
13 , parseLocalDeclaration
14 , parseLocalDeclaration
15+ , toPositionedError
14 ) where
16 ) where
15
17
16import Prelude hiding (lex)
18import Prelude hiding (lex)
@@ -270,11 +272,7 @@ parseModulesFromFiles
270 -> [(k, String)]
272 -> [(k, String)]
271 -> m [(k, Module)]
273 -> m [(k, Module)]
272parseModulesFromFiles toFilePath input =
274parseModulesFromFiles toFilePath input =
273− flip parU wrapError . inParallel . flip map input $ \(k, content) -> do
275+ flip parU wrapError . inParallel . flip map input $ parseModuleFromFile toFilePath
274− let filename = toFilePath k
275− ts <- lex filename content
276− m <- runTokenParser filename parseModule ts
277− return (k, m)
278 where
276 where
279 wrapError :: Either P.ParseError a -> m a
277 wrapError :: Either P.ParseError a -> m a
280 wrapError = either (throwError . MultipleErrors . pure . toPositionedError) return
278 wrapError = either (throwError . MultipleErrors . pure . toPositionedError) return
@@ -285,6 +283,18 @@ parseModulesFromFiles toFilePath input =
285 inParallel = withStrategy (parList rseq)
283 inParallel = withStrategy (parList rseq)
286
284
287
285
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@
288toPositionedError :: P.ParseError -> ErrorMessage
298toPositionedError :: P.ParseError -> ErrorMessage
289toPositionedError perr = ErrorMessage [ PositionedError (SourceSpan name start end) ] (ErrorParsingModule perr)
299toPositionedError perr = ErrorMessage [ PositionedError (SourceSpan name start end) ] (ErrorParsingModule perr)
290 where
300 where
@@ -562,3 +572,4 @@ parseBinderNoParens = P.choice
562--
572--
563parseGuard :: TokenParser Guard
573parseGuard :: TokenParser Guard
564parseGuard = pipe *> C.indented *> parseValue
574parseGuard = pipe *> C.indented *> parseValue
575+
tests/Language/PureScript/Ide/Integration.hs+17−9
@@ -33,6 +33,7 @@ module Language.PureScript.Ide.Integration
33 , loadModuleWithDeps
33 , loadModuleWithDeps
34 , getCwd
34 , getCwd
35 , getFlexCompletions
35 , getFlexCompletions
36+ , getFlexCompletionsInModule
36 , getType
37 , getType
37 , rebuildModule
38 , rebuildModule
38 , reset
39 , reset
@@ -49,7 +50,7 @@ import Data.Aeson
49import Data.Aeson.Types
50import Data.Aeson.Types
50import qualified Data.ByteString.Lazy.UTF8 as BSL
51import qualified Data.ByteString.Lazy.UTF8 as BSL
51import Data.Either (isRight)
52import Data.Either (isRight)
52−import Data.Maybe (fromJust, isNothing)
53+import Data.Maybe (fromJust, isNothing, fromMaybe)
53import qualified Data.Text as T
54import qualified Data.Text as T
54import qualified Data.Vector as V
55import qualified Data.Vector as V
55import Language.PureScript.Ide.Util
56import Language.PureScript.Ide.Util
@@ -95,11 +96,12 @@ compileTestProject :: IO Bool
95compileTestProject = do
96compileTestProject = do
96 pdir <- projectDirectory
97 pdir <- projectDirectory
97 (_, _, _, procHandle) <- createProcess $
98 (_, _, _, procHandle) <- createProcess $
98− (shell $ "psc " ++ fileGlob) {cwd=Just pdir
99+ (shell $ "psc " ++ fileGlob) { cwd = Just pdir
99− ,std_out=CreatePipe
100+ , std_out = CreatePipe
100− ,std_err=CreatePipe
101+ , std_err = CreatePipe
101 }
102 }
102− isSuccess <$> waitForProcess procHandle
103+ r <- tryNTimes 5 (getProcessExitCode procHandle)
104+ pure (fromMaybe False (isSuccess <$> r))
103
105
104tryNTimes :: Int -> IO (Maybe a) -> IO (Maybe a)
106tryNTimes :: Int -> IO (Maybe a) -> IO (Maybe a)
105tryNTimes 0 _ = pure Nothing
107tryNTimes 0 _ = pure Nothing
@@ -166,7 +168,10 @@ loadModule :: String -> IO String
166loadModule m = sendCommand $ load [m] []
168loadModule m = sendCommand $ load [m] []
167
169
168getFlexCompletions :: String -> IO [(String, String, String)]
170getFlexCompletions :: 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))
170
175
171getType :: String -> IO [(String, String, String)]
176getType :: String -> IO [(String, String, String)]
172getType q = parseCompletions <$> sendCommand (typeC q [])
177getType q = parseCompletions <$> sendCommand (typeC q [])
@@ -217,14 +222,17 @@ addImportW importCommand fp outfp =
217 ])
222 ])
218
223
219
224
220−completion :: [Value] -> Maybe Value -> Value
225+completion :: [Value] -> Maybe Value -> Maybe String -> Value
221−completion filters matcher =
226+completion filters matcher currentModule =
222 let
227 let
223 matcher' = case matcher of
228 matcher' = case matcher of
224 Nothing -> []
229 Nothing -> []
225 Just m -> ["matcher" .= m]
230 Just m -> ["matcher" .= m]
231+ currentModule' = case currentModule of
232+ Nothing -> []
233+ Just cm -> ["currentModule" .= cm]
226 in
234 in
227− commandWrapper "complete" (object $ "filters" .= filters : matcher')
235+ commandWrapper "complete" (object $ "filters" .= filters : matcher' ++ currentModule' )
228
236
229flexMatcher :: String -> Value
237flexMatcher :: String -> Value
230flexMatcher q = object [ "matcher" .= ("flex" :: String)
238flexMatcher q = object [ "matcher" .= ("flex" :: String)
tests/Language/PureScript/Ide/RebuildSpec.hs+7−1
@@ -11,7 +11,7 @@ shouldBeFailure :: String -> IO ()
11shouldBeFailure = shouldBe False . Integration.resultIsSuccess
11shouldBeFailure = shouldBe False . Integration.resultIsSuccess
12
12
13spec :: Spec
13spec :: Spec
14−spec = before_ Integration.reset $ describe "Rebuilding single modules" $ do
14+spec = before_ Integration.reset . describe "Rebuilding single modules" $ do
15 it "rebuilds a correct module without dependencies successfully" $ do
15 it "rebuilds a correct module without dependencies successfully" $ do
16 _ <- Integration.loadModuleWithDeps "RebuildSpecSingleModule"
16 _ <- Integration.loadModuleWithDeps "RebuildSpecSingleModule"
17 pdir <- Integration.projectDirectory
17 pdir <- Integration.projectDirectory
@@ -45,3 +45,9 @@ spec = before_ Integration.reset $ describe "Rebuilding single modules" $ do
45 pdir <- Integration.projectDirectory
45 pdir <- Integration.projectDirectory
46 let file = pdir </> "src" </> "RebuildSpecWithMissingForeign.fail"
46 let file = pdir </> "src" </> "RebuildSpecWithMissingForeign.fail"
47 Integration.rebuildModule file >>= shouldBeFailure
47 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
11import Test.Hspec
11import Test.Hspec
12
12
13testState :: PscIdeState
13testState :: 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
15
15
16defaultConfig :: Configuration
16defaultConfig :: Configuration
17defaultConfig =
17defaultConfig =
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