git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
Adds psc-ide to the project
e14e3f6Christoph Hegemann
bundle/README+9−6
@@ -10,12 +10,15 @@ Installation Instructions
10
10
11This bundle contains the following executables:
11This bundle contains the following executables:
12
12
13−- psc The PureScript compiler
13+- psc The PureScript compiler
14−- psci The PureScript interactive REPL (requires NodeJS)
14+- psci The PureScript interactive REPL (requires NodeJS)
15−- psc-docs A Markdown documentation generator for PureScript code
15+- psc-docs A Markdown documentation generator for PureScript code
16−- psc-bundle Bundles together CommonJS modules produced by `psc` into a
16+- psc-bundle Bundles together CommonJS modules produced by `psc` into a
17− single JavaScript file; useful for running in the browser.
17+ single JavaScript file; useful for running in the browser.
18−- psc-publish Generates documentation packages for uploading to Pursuit
18+- psc-publish Generates documentation packages for uploading to Pursuit
19+- psc-ide-server Provides Editor Support in the form of type information and
20+ autocompletion
21+- psc-ide-client Utility to query psc-ide-server
19
22
20Copy these files anywhere on your PATH.
23Copy these files anywhere on your PATH.
21
24
bundle/build.sh+12−8
@@ -21,16 +21,20 @@ strip ../dist/build/psci/psci
21strip ../dist/build/psc-docs/psc-docs
21strip ../dist/build/psc-docs/psc-docs
22strip ../dist/build/psc-publish/psc-publish
22strip ../dist/build/psc-publish/psc-publish
23strip ../dist/build/psc-bundle/psc-bundle
23strip ../dist/build/psc-bundle/psc-bundle
24+strip ../dist/build/psc-ide-server/psc-ide-server
25+strip ../dist/build/psc-ide-client/psc-ide-client
24
26
25# Copy files to staging directory
27# Copy files to staging directory
26−cp ../dist/build/psc/psc build/purescript/
28+cp ../dist/build/psc/psc build/purescript/
27−cp ../dist/build/psci/psci build/purescript/
29+cp ../dist/build/psci/psci build/purescript/
28−cp ../dist/build/psc-docs/psc-docs build/purescript/
30+cp ../dist/build/psc-docs/psc-docs build/purescript/
29−cp ../dist/build/psc-publish/psc-publish build/purescript/
31+cp ../dist/build/psc-publish/psc-publish build/purescript/
32+cp ../dist/build/psc-bundle/psc-bundle build/purescript/
33+cp ../dist/build/psc-ide-server/psc-ide-server build/purescript/
30−cp ../dist/build/psc-bundle/psc-bundle build/purescript/
34+cp ../dist/build/psc-ide-client/psc-ide-client build/purescript/
31−cp README build/purescript/
35+cp README build/purescript/
32−cp ../LICENSE build/purescript/
36+cp ../LICENSE build/purescript/
33−cp ../INSTALL.md build/purescript/
37+cp ../INSTALL.md build/purescript/
34
38
35# Make the binary bundle
39# Make the binary bundle
36pushd build > /dev/null
40pushd build > /dev/null
bundle/winbuild.sh+12−8
@@ -17,16 +17,20 @@ strip ../dist/build/psci/psci.exe
17strip ../dist/build/psc-docs/psc-docs.exe
17strip ../dist/build/psc-docs/psc-docs.exe
18strip ../dist/build/psc-publish/psc-publish.exe
18strip ../dist/build/psc-publish/psc-publish.exe
19strip ../dist/build/psc-bundle/psc-bundle.exe
19strip ../dist/build/psc-bundle/psc-bundle.exe
20+strip ../dist/build/psc-ide-server/psc-ide-server
21+strip ../dist/build/psc-ide-client/psc-ide-client
20
22
21# Copy files to staging directory
23# Copy files to staging directory
22−cp ../dist/build/psc/psc.exe build/purescript/
24+cp ../dist/build/psc/psc.exe build/purescript/
23−cp ../dist/build/psci/psci.exe build/purescript/
25+cp ../dist/build/psci/psci.exe build/purescript/
24−cp ../dist/build/psc-docs/psc-docs.exe build/purescript/
26+cp ../dist/build/psc-docs/psc-docs.exe build/purescript/
27+cp ../dist/build/psc-publish/psc-publish.exe build/purescript/
28+cp ../dist/build/psc-bundle/psc-bundle.exe build/purescript/
25−cp ../dist/build/psc-publish/psc-publish.exe build/purescript/
29+cp ../dist/build/psc-ide-server/psc-ide-server.exe build/purescript/
26−cp ../dist/build/psc-bundle/psc-bundle.exe build/purescript/
30+cp ../dist/build/psc-ide-client/psc-ide-client.exe build/purescript/
27−cp README build/purescript/
31+cp README build/purescript/
28−cp ../LICENSE build/purescript/
32+cp ../LICENSE build/purescript/
29−cp ../INSTALL.md build/purescript/
33+cp ../INSTALL.md build/purescript/
30
34
31# Make the binary bundle
35# Make the binary bundle
32pushd build
36pushd build
psc-ide-client/Main.hsnew file+57
@@ -0,0 +1,57 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Main where
3+
4+import Prelude ()
5+import Prelude.Compat
6+
7+import Control.Exception
8+import Data.Maybe (fromMaybe)
9+import Data.Text (Text)
10+import qualified Data.Text as T
11+import qualified Data.Text.IO as T
12+import Data.Version (showVersion)
13+import Network
14+import Options.Applicative
15+import System.Exit
16+import System.IO
17+
18+import qualified Paths_purescript as Paths
19+
20+data Options = Options
21+ { optionsPort :: Maybe Int
22+ }
23+
24+main :: IO ()
25+main = do
26+ Options port <- execParser opts
27+ let port' = PortNumber . fromIntegral $ fromMaybe 4242 port
28+ client port'
29+ where
30+ parser =
31+ Options <$>
32+ optional (option auto (long "port" <> short 'p'))
33+ opts = info (version <*> parser) mempty
34+ version = abortOption (InfoMsg (showVersion Paths.version)) $ long "version" <> help "Show the version number" <> hidden
35+
36+client :: PortID -> IO ()
37+client port = do
38+ h <-
39+ connectTo "localhost" port `catch`
40+ (\(SomeException e) ->
41+ putStrLn
42+ ("Couldn't connect to psc-ide-server on port: " ++
43+ show port ++ " Error: " ++ show e) >>
44+ exitFailure)
45+ cmd <- T.getLine
46+ -- Temporary fix for emacs windows bug
47+ let cleanedCmd = removeSurroundingTicks cmd
48+ --
49+ T.hPutStrLn h cleanedCmd
50+ res <- T.hGetLine h
51+ putStrLn (T.unpack res)
52+ hFlush stdout
53+ hClose h
54+
55+-- TODO: Fix this in the emacs plugin by using a real process over shellcommands
56+removeSurroundingTicks :: Text -> Text
57+removeSurroundingTicks = T.dropWhile (== '\'') . T.dropWhileEnd (== '\'')
psc-ide-server/Main.hsnew file+135
@@ -0,0 +1,135 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE MultiParamTypeClasses #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE PackageImports #-}
6+{-# LANGUAGE TemplateHaskell #-}
7+module Main where
8+
9+import Prelude ()
10+import Prelude.Compat
11+
12+import Control.Concurrent (forkFinally)
13+import Control.Concurrent.STM
14+import Control.Exception (bracketOnError)
15+import Control.Monad
16+import "monad-logger" Control.Monad.Logger
17+import Control.Monad.Reader
18+import Control.Monad.Trans.Except
19+import qualified Data.Text as T
20+import qualified Data.Text.IO as T
21+import Data.Version (showVersion)
22+import Language.PureScript.Ide
23+import Language.PureScript.Ide.CodecJSON
24+import Language.PureScript.Ide.Error
25+import Language.PureScript.Ide.Types
26+import Language.PureScript.Ide.Watcher
27+import Network hiding (socketPort)
28+import Network.BSD (getProtocolNumber)
29+import Network.Socket hiding (PortNumber, Type,
30+ accept, sClose)
31+import Options.Applicative
32+import System.Directory
33+import System.FilePath
34+import System.IO
35+
36+import qualified Paths_purescript as Paths
37+
38+-- "Borrowed" from the Idris Compiler
39+-- Copied from upstream impl of listenOn
40+-- bound to localhost interface instead of iNADDR_ANY
41+listenOnLocalhost :: PortID -> IO Socket
42+listenOnLocalhost (PortNumber port) = do
43+ proto <- getProtocolNumber "tcp"
44+ localhost <- inet_addr "127.0.0.1"
45+ bracketOnError
46+ (socket AF_INET Stream proto)
47+ sClose
48+ (\sock -> do
49+ setSocketOption sock ReuseAddr 1
50+ bindSocket sock (SockAddrInet port localhost)
51+ listen sock maxListenQueue
52+ pure sock)
53+listenOnLocalhost _ = error "Wrong Porttype"
54+
55+data Options = Options
56+ { optionsDirectory :: Maybe FilePath
57+ , optionsOutputPath :: FilePath
58+ , optionsPort :: PortID
59+ , optionsDebug :: Bool
60+ }
61+
62+main :: IO ()
63+main = do
64+ Options dir outputPath port debug <- execParser opts
65+ maybe (pure ()) setCurrentDirectory dir
66+ serverState <- newTVarIO emptyPscIdeState
67+ cwd <- getCurrentDirectory
68+ _ <- forkFinally (watcher serverState (cwd </> outputPath)) print
69+ let conf =
70+ Configuration
71+ {
72+ confDebug = debug
73+ , confOutputPath = outputPath
74+ }
75+ let env =
76+ PscIdeEnvironment
77+ {
78+ envStateVar = serverState
79+ , envConfiguration = conf
80+ }
81+ startServer port env
82+ where
83+ parser =
84+ Options <$>
85+ optional (strOption (long "directory" <> short 'd')) <*>
86+ strOption (long "output-directory" <> value "output/") <*>
87+ (PortNumber . fromIntegral <$>
88+ option auto (long "port" <> short 'p' <> value (4242 :: Integer))) <*>
89+ switch (long "debug")
90+ opts = info (version <*> parser) mempty
91+ version = abortOption
92+ (InfoMsg (showVersion Paths.version))
93+ (long "version" <> help "Show the version number")
94+
95+startServer :: PortID -> PscIdeEnvironment -> IO ()
96+startServer port env = withSocketsDo $ do
97+ sock <- listenOnLocalhost port
98+ runLogger (runReaderT (forever (loop sock)) env)
99+ where
100+ runLogger = runStdoutLoggingT . filterLogger (\_ _ -> confDebug (envConfiguration env))
101+
102+ loop :: (PscIde m, MonadLogger m) => Socket -> m ()
103+ loop sock = do
104+ (cmd,h) <- acceptCommand sock
105+ case decodeT cmd of
106+ Just cmd' -> do
107+ result <- runExceptT (handleCommand cmd')
108+ $(logDebug) ("Answer was: " <> T.pack (show result))
109+ liftIO (hFlush stdout)
110+ case result of
111+ -- What function can I use to clean this up?
112+ Right r -> liftIO $ T.hPutStrLn h (encodeT r)
113+ Left err -> liftIO $ T.hPutStrLn h (encodeT err)
114+ Nothing -> do
115+ $(logDebug) ("Parsing the command failed. Command: " <> cmd)
116+ liftIO $ do
117+ T.hPutStrLn h (encodeT (GeneralError "Error parsing Command."))
118+ hFlush stdout
119+ liftIO (hClose h)
120+
121+
122+acceptCommand :: (Applicative m, MonadIO m, MonadLogger m)
123+ => Socket -> m (T.Text, Handle)
124+acceptCommand sock = do
125+ h <- acceptConnection
126+ $(logDebug) "Accepted a connection"
127+ cmd <- liftIO (T.hGetLine h)
128+ $(logDebug) cmd
129+ pure (cmd, h)
130+ where
131+ acceptConnection = liftIO $ do
132+ (h,_,_) <- accept sock
133+ hSetEncoding h utf8
134+ hSetBuffering h LineBuffering
135+ pure h
psc-ide-server/PROTOCOL.mdnew file+409
@@ -0,0 +1,409 @@
1+# Protocol
2+
3+Encode the following JSON formats into a single line string and pass them to
4+`psc-ide-client`s stdin. You can then read the result from `psc-ide-client`s
5+stdout as a single line. The result needs to be unwrapped from the "wrapper"
6+which separates success from failure. This wrapper is described at the end of
7+this document.
8+
9+## Command:
10+### Load
11+The `load` command "loads" the requested modules into the server
12+for completion and type info.
13+
14+**Params:**
15+ - `modules :: (optional) [ModuleName]`: A list of modules to load.
16+ psc-ide-server will try to parse all the declarations in these modules
17+ - `dependencies :: (optional) [ModuleName]`: A list of modules to load
18+ including their dependencies. In contrast to the `module` field, all the
19+ imports in these Modules will also be loaded.
20+
21+```json
22+{
23+ "command": "load",
24+ "params": {
25+ "modules": (optional)["Module.Name1", "Module.Name2"],
26+ "dependencies": (optional)["Module.Name3"]
27+ }
28+}
29+```
30+
31+**Result:**
32+
33+The Load Command returns a string.
34+
35+### Type
36+The `type` command looks up the type for a given identifier.
37+
38+**Params:**
39+ - `search :: String`: The identifier to look for. Only matches on equality.
40+ - `filters :: [Filter]`: These filters will be applied before looking for the
41+ identifier. These filters get combined with *AND*, so a candidate must match *ALL*
42+ of them to be eligible.
43+```json
44+{
45+ "command": "type",
46+ "params": {
47+ "search": "filterM",
48+ "filters": [Filter]
49+ }
50+}
51+```
52+
53+**Result:**
54+The possible types are returned in the same format as completions
55+
56+### Complete
57+The `complete` command looks up possible completions/corrections.
58+
59+**Params**:
60+ - `filters :: [Filter]`: The same as for the `type` command. A candidate must match
61+ all filters.
62+ - `matcher :: (optional) Matcher`: The strategy used for matching candidates after filtering.
63+ Results are scored internally and will be returned in the descending order where
64+ the nth element is better then the n+1-th.
65+
66+ If no matcher is given every candidate, that passes the filters, is returned in no
67+ particular order.
68+```json
69+{
70+ "command": "complete",
71+ "params": {
72+ "filters": [Filter],
73+ "matcher": (optional) Matcher
74+ }
75+}
76+```
77+
78+**Result:**
79+
80+The following format is returned as the Result:
81+
82+```json
83+[
84+ {
85+ "module": "Data.Array",
86+ "identifier": "filter",
87+ "type": "forall a. (a -> Boolean) -> Array a -> Array a"
88+ }
89+]
90+```
91+
92+
93+### CaseSplit
94+
95+The CaseSplit command takes a line of source code, an area in that line of code
96+and replaces it with all patterns for a given type. The parameter `annotations`
97+is used to turn type annotations on or off for the constructor fields.
98+
99+```json
100+{
101+ "command": "caseSplit",
102+ "params": {
103+ "line": "elem a as",
104+ "begin": 8,
105+ "end": 10,
106+ "annotations": true,
107+ "type": "List"
108+ }
109+}
110+```
111+
112+**Result:**
113+
114+The following format is returned as the Result:
115+
116+```json
117+[
118+ "elem a Nil",
119+ "elem a (Cons (_ :: a) (_ :: List a))"
120+]
121+```
122+You should then be able to replace the affected line of code in the editor with the new suggestions.
123+
124+### Add Clause
125+
126+The AddClause command takes a typedeclaration and generates a function template for the given type.
127+The `annotations` option turns type annotations on or off for the function arguments.
128+
129+```json
130+{
131+ "command": "addClause",
132+ "params": {
133+ "line": "elem :: forall a. (Eq a) => a -> List a",
134+ "annotations": true
135+ }
136+}
137+```
138+
139+**Result:**
140+
141+The following format is returned as the Result:
142+
143+```json
144+[
145+ "elem :: forall a. (Eq a) => a -> List a",
146+ "elem ( _ :: a) = ?elem"
147+]
148+```
149+You should then be able to replace the affected line of code in the editor with the new suggestions.
150+
151+### Pursuit
152+The `pursuit` command looks up the packages/completions for a given identifier from Pursuit.
153+
154+**Params:**
155+ - `query :: String`: With `type: "package"` this should be a module name. With
156+ `type: "completion"` this can be any string to look up.
157+ - `type :: String`: Takes the following values:
158+ - `package`: Looks for packages that contain the given module name.
159+ - `completion` Looks for declarations for the query from Pursuit.
160+
161+```json
162+{
163+ "command": "pursuit",
164+ "params": {
165+ "query": "Data.Array",
166+ "type": "package"
167+ }
168+}
169+```
170+
171+**Result:**
172+
173+`package` returns:
174+
175+```json
176+[
177+ {
178+ "module": "Module1.Name",
179+ "package": "purescript-packagename"
180+ }
181+]
182+```
183+
184+`completion` returns:
185+
186+```json
187+[
188+ {
189+ "module": "Data.Array",
190+ "identifier": "filter",
191+ "type": "forall a. (a -> Boolean) -> Array a -> Array a",
192+ "package": "purescript-arrays"
193+ }
194+]
195+```
196+
197+### List
198+
199+#### Loaded Modules
200+
201+`list` of type `loadedModules` lists all loaded modules (This means they can be searched for completions etc)
202+
203+```json
204+{
205+ "command": "list",
206+ "params": {
207+ "type": "loadedModules"
208+ }
209+}
210+```
211+
212+#### Response:
213+
214+The list loadedModules command returns a list of strings.
215+
216+#### Available Modules
217+
218+`list` of type `availableModules` lists all available modules. (This basically
219+means the contents of the `output/` folder.))
220+
221+```json
222+{
223+ "command": "list",
224+ "params": {
225+ "type": "availableModules"
226+ }
227+}
228+```
229+
230+#### Response:
231+
232+The list availableModules command returns a list of strings.
233+
234+#### Imports
235+
236+The list commmand can also list the imports for a given file.
237+
238+```json
239+{
240+ "command": "list",
241+ "params": {
242+ "type": "import",
243+ "file": "/home/kritzcreek/Documents/psc-ide/examples/Main.purs"
244+ }
245+}
246+```
247+
248+#### Response:
249+
250+The list import command returns a list of imports where imports are of the following form:
251+
252+Implicit Import(`import Data.Array`):
253+```json
254+[
255+ {
256+ "module": "Data.Array",
257+ "importType": "implicit"
258+ }
259+]
260+```
261+
262+Implicit qualified Import(`import qualified Data.Array as A`):
263+```json
264+[
265+ {
266+ "module": "Data.Array",
267+ "importType": "implicit",
268+ "qualifier": "A"
269+ }
270+]
271+```
272+
273+Explicit Import(`import Data.Array (filter, filterM, join)`):
274+```json
275+[
276+ {
277+ "module": "Data.Array",
278+ "importType": "explicit",
279+ "identifiers": ["filter", "filterM", "join"]
280+ }
281+]
282+```
283+
284+Hiding Import(`import Data.Array hiding (filter, filterM, join)`):
285+```json
286+[
287+ {
288+ "module": "Data.Array",
289+ "importType": "hiding",
290+ "identifiers": ["filter", "filterM", "join"]
291+ }
292+]
293+```
294+### Cwd/Quit
295+`cwd` returns the working directory of the server(should be your project root).
296+
297+`quit` quits the server.
298+
299+```json
300+{
301+ "command": "cwd|quit"
302+}
303+```
304+
305+**Result:**
306+These commands return strings.
307+
308+## Filter:
309+
310+### Exact filter
311+The Exact filter only keeps identifiers that are equal to the search term.
312+
313+```json
314+{
315+ "filter": "exact",
316+ "params": {
317+ "search": "filterM"
318+ }
319+}
320+```
321+### Prefix filter
322+The Prefix filter keeps identifiers/modules/data declarations that
323+are prefixed by the search term.
324+
325+```json
326+{
327+ "filter": "prefix",
328+ "params": {
329+ "search": "filt"
330+ }
331+}
332+```
333+
334+### Module filter
335+The Module filter only keeps identifiers that appear in the listed modules.
336+
337+```json
338+{
339+ "filter": "modules",
340+ "params": {
341+ "modules": ["My.Module"]
342+ }
343+}
344+```
345+
346+### Dependency filter
347+The Dependency filter only keeps identifiers that appear in the listed modules
348+and in any of their dependencies/imports.
349+
350+```json
351+{
352+ "filter": "dependencies",
353+ "params": {
354+ "modules": ["My.Module"]
355+ }
356+}
357+```
358+
359+## Matcher:
360+
361+### Flex matcher
362+Matches any occurence of the search string with intersections
363+
364+The scoring measures how far the matches span the string, where
365+closer is better. The matches then get sorted with highest score first.
366+
367+Examples:
368+- flMa matches **fl**ex**Ma**tcher. Score: 14.28
369+- sons matches **so**rtCompletio**ns**. Score: 6.25
370+```json
371+
372+{
373+ "matcher": "flex",
374+ "params": {
375+ "search": "filt"
376+ }
377+}
378+```
379+
380+### Distance Matcher
381+
382+The Distance matcher is meant to provide corrections for typos. It calculates
383+the edit distance in between the search and the loaded identifiers.
384+
385+```json
386+{
387+ "matcher": "distance",
388+ "params": {
389+ "search": "dilterM",
390+ "maximumDistance": 3
391+ }
392+}
393+```
394+
395+## Responses
396+
397+All Responses are wrapped in the following format:
398+
399+```json
400+{
401+ "resultType": "success|error",
402+ "result": Result|Error
403+}
404+```
405+
406+### Error
407+
408+Errors at this point are merely Error strings. Newlines are escaped like `\n`
409+and should be taken care of by the editor-plugin.
psc-ide-server/README.mdnew file+40
@@ -0,0 +1,40 @@
1+psc-ide
2+===
3+
4+A tool which provides editor support for the PureScript programming language.
5+
6+## Editor Integration
7+* [@epost](https://github.com/epost) wrote a plugin to integrate psc-ide with Emacs at https://github.com/epost/psc-ide-emacs.
8+* Atom integration is available with https://github.com/nwolverson/atom-ide-purescript.
9+* Visual Studio Code integration is available with https://github.com/nwolverson/vscode-ide-purescript.
10+* Vim integration is available here: https://github.com/FrigoEU/psc-ide-vim.
11+
12+## Running the Server
13+Start the server by running the `psc-ide-server` executable.
14+It supports the following options:
15+
16+- `-p / --port` specify a port. Defaults to 4242
17+- `-d / --directory` specify the toplevel directory of your project. Defaults to
18+ the current directory
19+- `--output-directory`: Specify where to look for compiled output inside your
20+ project directory. Defaults to `output/`, relative to either the current
21+ directory or the directory specified by `-d`.
22+- `--debug`: Enables some logging meant for debugging
23+- `--version`: Output psc-ide version
24+
25+## Issuing queries
26+
27+After you started the server you can start issuing requests using
28+`psc-ide-client`. Make sure you start by loading the modules before you try to
29+query them.
30+
31+`psc-ide-server` expects the build externs.purs inside the `output/` folder of
32+your project after running `pulp build` or `psc-make` respectively.
33+
34+(If you changed the port of the server you can change the port for
35+`psc-ide-client` by using the -p option accordingly)
36+
37+## Protocol
38+
39+For documentation about the protocol have a look at:
40+[PROTOCOL.md](PROTOCOL.md)
purescript.cabal+63−3
@@ -14,7 +14,8 @@ Homepage: http://www.purescript.org/
14author: Phil Freeman <paf31@cantab.net>,
14author: Phil Freeman <paf31@cantab.net>,
15 Gary Burgess <gary.burgess@gmail.com>,
15 Gary Burgess <gary.burgess@gmail.com>,
16 Hardy Jones <jones3.hardy@gmail.com>,
16 Hardy Jones <jones3.hardy@gmail.com>,
17− Harry Garrood <harry@garrood.me>
17+ Harry Garrood <harry@garrood.me>,
18+ Christoph Hegemann <christoph.hegemann1337@gmail.com>
18
19
19tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.1
20tested-with: GHC==7.8.4, GHC==7.10.3, GHC==8.0.1
20
21
@@ -80,7 +81,15 @@ library
80 safe >= 0.3.9 && < 0.4,
81 safe >= 0.3.9 && < 0.4,
81 semigroups >= 0.16.2 && < 0.19,
82 semigroups >= 0.16.2 && < 0.19,
82 parallel >= 3.2 && < 3.3,
83 parallel >= 3.2 && < 3.3,
83− sourcemap >= 0.1.6
84+ sourcemap >= 0.1.6,
85+ stm >= 0.2.4.0,
86+ regex-tdfa -any,
87+ edit-distance -any,
88+ fsnotify >= 0.2.1,
89+ monad-logger >= 0.3 && < 0.4,
90+ pipes >= 4.0.0 && < 4.2.0 ,
91+ pipes-http -any,
92+ http-types -any
84
93
85 exposed-modules: Language.PureScript
94 exposed-modules: Language.PureScript
86 Language.PureScript.AST
95 Language.PureScript.AST
@@ -188,6 +197,22 @@ library
188 Language.PureScript.Publish.ErrorsWarnings
197 Language.PureScript.Publish.ErrorsWarnings
189 Language.PureScript.Publish.BoxesHelpers
198 Language.PureScript.Publish.BoxesHelpers
190
199
200+ Language.PureScript.Ide
201+ Language.PureScript.Ide.Command
202+ Language.PureScript.Ide.Externs
203+ Language.PureScript.Ide.Error
204+ Language.PureScript.Ide.CodecJSON
205+ Language.PureScript.Ide.Pursuit
206+ Language.PureScript.Ide.Completion
207+ Language.PureScript.Ide.Matcher
208+ Language.PureScript.Ide.Filter
209+ Language.PureScript.Ide.Types
210+ Language.PureScript.Ide.State
211+ Language.PureScript.Ide.CaseSplit
212+ Language.PureScript.Ide.SourceFile
213+ Language.PureScript.Ide.Watcher
214+ Language.PureScript.Ide.Reexports
215+
191 Control.Monad.Logger
216 Control.Monad.Logger
192 Control.Monad.Supply
217 Control.Monad.Supply
193 Control.Monad.Supply.Class
218 Control.Monad.Supply.Class
@@ -286,13 +311,46 @@ executable psc-bundle
286 ghc-options: -Wall -O2
311 ghc-options: -Wall -O2
287 hs-source-dirs: psc-bundle
312 hs-source-dirs: psc-bundle
288
313
314+executable psc-ide-server
315+ main-is: Main.hs
316+ other-modules:
317+ other-extensions:
318+ build-depends: base >=4 && <5
319+ , purescript -any
320+ , directory -any
321+ , filepath -any
322+ , monad-logger -any
323+ , mtl -any
324+ , transformers -any
325+ , transformers-compat -any
326+ , network -any
327+ , optparse-applicative >= 0.10.0
328+ , stm -any
329+ , text -any
330+ , base-compat >=0.6.0
331+ ghc-options: -Wall -O2 -threaded
332+ hs-source-dirs: psc-ide-server
333+
334+executable psc-ide-client
335+ main-is: Main.hs
336+ other-modules:
337+ other-extensions:
338+ build-depends: base >=4 && <5
339+ , mtl -any
340+ , text -any
341+ , optparse-applicative >= 0.10.0
342+ , network -any
343+ , base-compat >=0.6.0
344+ ghc-options: -Wall -O2
345+ hs-source-dirs: psc-ide-client
346+
289test-suite tests
347test-suite tests
290 build-depends: base >=4 && <5, containers -any, directory -any,
348 build-depends: base >=4 && <5, containers -any, directory -any,
291 filepath -any, mtl -any, parsec -any, purescript -any,
349 filepath -any, mtl -any, parsec -any, purescript -any,
292 transformers -any, process -any, transformers-compat -any, time -any,
350 transformers -any, process -any, transformers-compat -any, time -any,
293 Glob -any, aeson-better-errors -any, bytestring -any, aeson -any,
351 Glob -any, aeson-better-errors -any, bytestring -any, aeson -any,
294 base-compat -any, haskeline >= 0.7.0.0, optparse-applicative -any,
352 base-compat -any, haskeline >= 0.7.0.0, optparse-applicative -any,
295− boxes -any, HUnit -any
353+ boxes -any, HUnit -any, hspec -any, hspec-discover -any, stm -any, text -any
296 ghc-options: -Wall
354 ghc-options: -Wall
297 type: exitcode-stdio-1.0
355 type: exitcode-stdio-1.0
298 main-is: Main.hs
356 main-is: Main.hs
@@ -301,5 +359,7 @@ test-suite tests
301 TestDocs
359 TestDocs
302 TestPscPublish
360 TestPscPublish
303 TestPsci
361 TestPsci
362+ TestPscIde
363+ PscIdeSpec
304 buildable: True
364 buildable: True
305 hs-source-dirs: tests psci
365 hs-source-dirs: tests psci
src/Language/PureScript/Ide.hsnew file+199
@@ -0,0 +1,199 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE MultiParamTypeClasses #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE PackageImports #-}
6+{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE TupleSections #-}
8+
9+module Language.PureScript.Ide where
10+
11+import Prelude ()
12+import Prelude.Compat
13+
14+import Control.Monad.Error.Class
15+import Control.Monad.IO.Class
16+import "monad-logger" Control.Monad.Logger
17+import Control.Monad.Reader.Class
18+import Data.Foldable
19+import qualified Data.Map.Lazy as M
20+import Data.Maybe (catMaybes, mapMaybe)
21+import Data.Monoid
22+import Data.Text (Text)
23+import qualified Data.Text as T
24+import qualified Language.PureScript.Ide.CaseSplit as CS
25+import Language.PureScript.Ide.Command
26+import Language.PureScript.Ide.Completion
27+import Language.PureScript.Ide.Error
28+import Language.PureScript.Ide.Externs
29+import Language.PureScript.Ide.Filter
30+import Language.PureScript.Ide.Matcher
31+import Language.PureScript.Ide.Pursuit
32+import Language.PureScript.Ide.Reexports
33+import Language.PureScript.Ide.SourceFile
34+import Language.PureScript.Ide.State
35+import Language.PureScript.Ide.Types
36+import System.Directory
37+import System.FilePath
38+import System.Exit
39+
40+
41+handleCommand :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
42+ Command -> m Success
43+handleCommand (Load modules deps) =
44+ loadModulesAndDeps modules deps
45+handleCommand (Type search filters) =
46+ findType search filters
47+handleCommand (Complete filters matcher) =
48+ findCompletions filters matcher
49+handleCommand (Pursuit query Package) =
50+ findPursuitPackages query
51+handleCommand (Pursuit query Identifier) =
52+ findPursuitCompletions query
53+handleCommand (List LoadedModules) =
54+ printModules
55+handleCommand (List AvailableModules) =
56+ listAvailableModules
57+handleCommand (List (Imports fp)) =
58+ importsForFile fp
59+handleCommand (CaseSplit l b e wca t) =
60+ caseSplit l b e wca t
61+handleCommand (AddClause l wca) =
62+ pure $ addClause l wca
63+handleCommand Cwd =
64+ TextResult . T.pack <$> liftIO getCurrentDirectory
65+handleCommand Quit = liftIO exitSuccess
66+
67+findCompletions :: (PscIde m, MonadLogger m) =>
68+ [Filter] -> Matcher -> m Success
69+findCompletions filters matcher =
70+ CompletionResult . getCompletions filters matcher <$> getAllModulesWithReexports
71+
72+findType :: (PscIde m, MonadLogger m) =>
73+ DeclIdent -> [Filter] -> m Success
74+findType search filters =
75+ CompletionResult . getExactMatches search filters <$> getAllModulesWithReexports
76+
77+findPursuitCompletions :: (Applicative m, MonadIO m, MonadLogger m) =>
78+ PursuitQuery -> m Success
79+findPursuitCompletions (PursuitQuery q) =
80+ PursuitResult <$> liftIO (searchPursuitForDeclarations q)
81+
82+findPursuitPackages :: (Applicative m, MonadIO m, MonadLogger m) =>
83+ PursuitQuery -> m Success
84+findPursuitPackages (PursuitQuery q) =
85+ PursuitResult <$> liftIO (findPackagesForModuleIdent q)
86+
87+loadExtern ::(PscIde m, MonadLogger m, MonadError PscIdeError m) =>
88+ FilePath -> m ()
89+loadExtern fp = do
90+ m <- readExternFile fp
91+ insertModule m
92+
93+printModules :: (PscIde m) => m Success
94+printModules = printModules' <$> getPscIdeState
95+
96+printModules' :: M.Map ModuleIdent [ExternDecl] -> Success
97+printModules' = ModuleList . M.keys
98+
99+listAvailableModules :: PscIde m => m Success
100+listAvailableModules = do
101+ outputPath <- confOutputPath . envConfiguration <$> ask
102+ liftIO $ do
103+ cwd <- getCurrentDirectory
104+ dirs <- getDirectoryContents (cwd </> outputPath)
105+ return (ModuleList (listAvailableModules' dirs))
106+
107+listAvailableModules' :: [FilePath] -> [Text]
108+listAvailableModules' dirs =
109+ let cleanedModules = filter (`notElem` [".", ".."]) dirs
110+ in map T.pack cleanedModules
111+
112+caseSplit :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
113+ Text -> Int -> Int -> CS.WildcardAnnotations -> Text -> m Success
114+caseSplit l b e csa t = do
115+ patterns <- CS.makePattern l b e csa <$> CS.caseSplit t
116+ pure (MultilineTextResult patterns)
117+
118+addClause :: Text -> CS.WildcardAnnotations -> Success
119+addClause t wca = MultilineTextResult (CS.addClause t wca)
120+
121+importsForFile :: (Applicative m, MonadIO m, MonadLogger m, MonadError PscIdeError m) =>
122+ FilePath -> m Success
123+importsForFile fp = do
124+ imports <- getImportsForFile fp
125+ pure (ImportList imports)
126+
127+-- | The first argument is a set of modules to load. The second argument
128+-- denotes modules for which to load dependencies
129+loadModulesAndDeps :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
130+ [ModuleIdent] -> [ModuleIdent] -> m Success
131+loadModulesAndDeps mods deps = do
132+ r1 <- mapM loadModule (mods ++ deps)
133+ r2 <- mapM loadModuleDependencies deps
134+ let moduleResults = T.concat r1
135+ let dependencyResults = T.concat r2
136+ pure (TextResult (moduleResults <> ", " <> dependencyResults))
137+
138+loadModuleDependencies ::(PscIde m, MonadLogger m, MonadError PscIdeError m) =>
139+ ModuleIdent -> m Text
140+loadModuleDependencies moduleName = do
141+ m <- getModule moduleName
142+ case getDependenciesForModule <$> m of
143+ Just deps -> do
144+ mapM_ loadModule deps
145+ -- We need to load the modules, that get reexported from the dependencies
146+ depModules <- catMaybes <$> mapM getModule deps
147+ -- What to do with errors here? This basically means a reexported dependency
148+ -- doesn't exist in the output/ folder
149+ traverse_ loadReexports depModules
150+ pure ("Dependencies for " <> moduleName <> " loaded.")
151+ Nothing -> throwError (ModuleNotFound moduleName)
152+
153+loadReexports :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
154+ Module -> m [ModuleIdent]
155+loadReexports m = case getReexports m of
156+ [] -> pure []
157+ exportDeps -> do
158+ -- I'm fine with this crashing on a failed pattern match.
159+ -- If this ever fails I'll need to look at GADTs
160+ let reexports = map (\(Export mn) -> mn) exportDeps
161+ $(logDebug) ("Loading reexports for module: " <> fst m <>
162+ " reexports: " <> T.intercalate ", " reexports)
163+ traverse_ loadModule reexports
164+ exportDepsModules <- catMaybes <$> traverse getModule reexports
165+ exportDepDeps <- traverse loadReexports exportDepsModules
166+ return $ concat exportDepDeps
167+
168+getDependenciesForModule :: Module -> [ModuleIdent]
169+getDependenciesForModule (_, decls) = mapMaybe getDependencyName decls
170+ where getDependencyName (Dependency dependencyName _ _) = Just dependencyName
171+ getDependencyName _ = Nothing
172+
173+loadModule :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
174+ ModuleIdent -> m Text
175+loadModule "Prim" = pure "Prim won't be loaded"
176+loadModule mn = do
177+ path <- filePathFromModule mn
178+ loadExtern path
179+ $(logDebug) ("Loaded extern file at: " <> T.pack path)
180+ pure ("Loaded extern file at: " <> T.pack path)
181+
182+filePathFromModule :: (PscIde m, MonadError PscIdeError m) =>
183+ ModuleIdent -> m FilePath
184+filePathFromModule moduleName = do
185+ outputPath <- confOutputPath . envConfiguration <$> ask
186+ cwd <- liftIO getCurrentDirectory
187+ let path = cwd </> outputPath </> T.unpack moduleName </> "externs.json"
188+ ex <- liftIO $ doesFileExist path
189+ if ex
190+ then pure path
191+ else throwError (ModuleFileNotFound moduleName)
192+
193+-- | Taken from Data.Either.Utils
194+maybeToEither :: MonadError e m =>
195+ e -- ^ (Left e) will be returned if the Maybe value is Nothing
196+ -> Maybe a -- ^ (Right a) will be returned if this is (Just a)
197+ -> m a
198+maybeToEither errorval Nothing = throwError errorval
199+maybeToEither _ (Just normalval) = return normalval
src/Language/PureScript/Ide/CaseSplit.hsnew file+157
@@ -0,0 +1,157 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE DataKinds #-}
3+{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE LambdaCase #-}
5+{-# LANGUAGE MultiParamTypeClasses #-}
6+{-# LANGUAGE OverloadedStrings #-}
7+{-# LANGUAGE PackageImports #-}
8+{-# LANGUAGE RecordWildCards #-}
9+
10+module Language.PureScript.Ide.CaseSplit
11+ ( WildcardAnnotations()
12+ , explicitAnnotations
13+ , noAnnotations
14+ , makePattern
15+ , addClause
16+ , caseSplit
17+ ) where
18+
19+import Prelude ()
20+import Prelude.Compat hiding (lex)
21+
22+import Control.Monad.Error.Class
23+import "monad-logger" Control.Monad.Logger
24+import Data.List (find)
25+import Data.Monoid
26+import Data.Text (Text)
27+import qualified Data.Text as T
28+import Language.PureScript.AST
29+import Language.PureScript.Environment
30+import Language.PureScript.Externs
31+import Language.PureScript.Ide.Error
32+import Language.PureScript.Ide.Externs (unwrapPositioned)
33+import Language.PureScript.Ide.State
34+import Language.PureScript.Ide.Types hiding (Type)
35+import Language.PureScript.Names
36+import Language.PureScript.Parser.Common (runTokenParser)
37+import Language.PureScript.Parser.Declarations
38+import Language.PureScript.Parser.Lexer (lex)
39+import Language.PureScript.Parser.Types
40+import Language.PureScript.Pretty
41+import Language.PureScript.Types
42+import Text.Parsec as P
43+
44+type Constructor = (ProperName 'ConstructorName, [Type])
45+
46+newtype WildcardAnnotations = WildcardAnnotations Bool
47+
48+explicitAnnotations :: WildcardAnnotations
49+explicitAnnotations = WildcardAnnotations True
50+
51+noAnnotations :: WildcardAnnotations
52+noAnnotations = WildcardAnnotations False
53+
54+caseSplit :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
55+ Text -> m [Constructor]
56+caseSplit q = do
57+ (tc, args) <- splitTypeConstructor (parseType' (T.unpack q))
58+ (EDType _ _ (DataType typeVars ctors)) <- findTypeDeclaration tc
59+ let applyTypeVars = everywhereOnTypes (replaceAllTypeVars (zip (map fst typeVars) args))
60+ let appliedCtors = map (\(n, ts) -> (n, map applyTypeVars ts)) ctors
61+ pure appliedCtors
62+
63+{- ["EDType {
64+ edTypeName = ProperName {runProperName = \"Either\"}
65+ , edTypeKind = FunKind Star (FunKind Star Star)
66+ , edTypeDeclarationKind =
67+ DataType [(\"a\",Just Star),(\"b\",Just Star)]
68+ [(ProperName {runProperName = \"Left\"},[TypeVar \"a\"])
69+ ,(ProperName {runProperName = \"Right\"},[TypeVar \"b\"])]}"]
70+-}
71+
72+findTypeDeclaration :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
73+ ProperName 'TypeName -> m ExternsDeclaration
74+findTypeDeclaration q = do
75+ efs <- getExternFiles
76+ let m = getFirst $ foldMap (findTypeDeclaration' q) efs
77+ case m of
78+ Just mn -> pure mn
79+ Nothing -> throwError (GeneralError "Not Found")
80+
81+findTypeDeclaration' ::
82+ ProperName 'TypeName
83+ -> ExternsFile
84+ -> First ExternsDeclaration
85+findTypeDeclaration' t ExternsFile{..} =
86+ First $ find (\case
87+ EDType tn _ _ -> tn == t
88+ _ -> False) efDeclarations
89+
90+splitTypeConstructor :: (Applicative m, MonadError PscIdeError m) =>
91+ Type -> m (ProperName 'TypeName, [Type])
92+splitTypeConstructor = go []
93+ where
94+ go acc (TypeApp ty arg) = go (arg : acc) ty
95+ go acc (TypeConstructor tc) = pure (disqualify tc, acc)
96+ go _ _ = throwError (GeneralError "Failed to read TypeConstructor")
97+
98+prettyCtor :: WildcardAnnotations -> Constructor -> Text
99+prettyCtor _ (ctorName, []) = T.pack (runProperName ctorName)
100+prettyCtor wsa (ctorName, ctorArgs) =
101+ "("<> T.pack (runProperName ctorName) <> " "
102+ <> T.unwords (map (prettyPrintWildcard wsa) ctorArgs) <>")"
103+
104+prettyPrintWildcard :: WildcardAnnotations -> Type -> Text
105+prettyPrintWildcard (WildcardAnnotations True) = prettyWildcard
106+prettyPrintWildcard (WildcardAnnotations False) = const "_"
107+
108+prettyWildcard :: Type -> Text
109+prettyWildcard t = "( _ :: " <> T.strip (T.pack (prettyPrintTypeAtom t)) <> ")"
110+
111+-- | Constructs Patterns to insert into a sourcefile
112+makePattern :: Text -- ^ Current line
113+ -> Int -- ^ Begin of the split
114+ -> Int -- ^ End of the split
115+ -> WildcardAnnotations -- ^ Whether to explicitly type the splits
116+ -> [Constructor] -- ^ Constructors to split
117+ -> [Text]
118+makePattern t x y wsa = makePattern' (T.take x t) (T.drop y t)
119+ where
120+ makePattern' lhs rhs = map (\ctor -> lhs <> prettyCtor wsa ctor <> rhs)
121+
122+addClause :: Text -> WildcardAnnotations -> [Text]
123+addClause s wca =
124+ let (fName, fType) = parseTypeDeclaration' (T.unpack s)
125+ (args, _) = splitFunctionType fType
126+ template = T.pack (runIdent fName) <> " " <>
127+ T.unwords (map (prettyPrintWildcard wca) args) <>
128+ " = ?" <> (T.strip . T.pack . runIdent $ fName)
129+ in [s, template]
130+
131+parseType' :: String -> Type
132+parseType' s = let (Right t) = do
133+ ts <- lex "" s
134+ runTokenParser "" (parseType <* P.eof) ts
135+ in t
136+
137+parseTypeDeclaration' :: String -> (Ident, Type)
138+parseTypeDeclaration' s =
139+ let x = do
140+ ts <- lex "" s
141+ runTokenParser "" (parseDeclaration <* P.eof) ts
142+ in
143+ case unwrapPositioned <$> x of
144+ Right (TypeDeclaration i t) -> (i, t)
145+ y -> error (show y)
146+
147+splitFunctionType :: Type -> ([Type], Type)
148+splitFunctionType t = (arguments, returns)
149+ where
150+ returns = last splitted
151+ arguments = init splitted
152+ splitted = splitType' t
153+ splitType' (ForAll _ t' _) = splitType' t'
154+ splitType' (ConstrainedType _ t') = splitType' t'
155+ splitType' (TypeApp (TypeApp t' lhs) rhs)
156+ | t' == tyFunction = lhs : splitType' rhs
157+ splitType' t' = [t']
src/Language/PureScript/Ide/CodecJSON.hsnew file+13
@@ -0,0 +1,13 @@
1+module Language.PureScript.Ide.CodecJSON where
2+
3+import Data.Aeson
4+import Data.Text (Text())
5+import Data.Text.Lazy (toStrict, fromStrict)
6+import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)
7+
8+encodeT :: (ToJSON a) => a -> Text
9+encodeT = toStrict . decodeUtf8 . encode
10+
11+decodeT :: (FromJSON a) => Text -> Maybe a
12+decodeT = decode . encodeUtf8 . fromStrict
13+
src/Language/PureScript/Ide/Command.hsnew file+101
@@ -0,0 +1,101 @@
1+{-# LANGUAGE MultiParamTypeClasses #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+
5+module Language.PureScript.Ide.Command where
6+
7+import Prelude ()
8+import Prelude.Compat
9+
10+import Control.Monad
11+import Data.Aeson
12+import Data.Maybe
13+import Data.Text (Text)
14+import Language.PureScript.Ide.CaseSplit
15+import Language.PureScript.Ide.Filter
16+import Language.PureScript.Ide.Matcher
17+import Language.PureScript.Ide.Types
18+
19+data Command
20+ = Load { loadModules :: [ModuleIdent]
21+ , loadDependencies :: [ModuleIdent]}
22+ | Type { typeSearch :: DeclIdent
23+ , typeFilters :: [Filter]}
24+ | Complete { completeFilters :: [Filter]
25+ , completeMatcher :: Matcher}
26+ | Pursuit { pursuitQuery :: PursuitQuery
27+ , pursuitSearchType :: PursuitSearchType}
28+ | List {listType :: ListType}
29+ | CaseSplit {
30+ caseSplitLine :: Text
31+ , caseSplitBegin :: Int
32+ , caseSplitEnd :: Int
33+ , caseSplitAnnotations :: WildcardAnnotations
34+ , caseSplitType :: Type}
35+ | AddClause {
36+ addClauseLine :: Text
37+ , addClauseAnnotations :: WildcardAnnotations}
38+ | Cwd
39+ | Quit
40+
41+data ListType = LoadedModules | Imports FilePath | AvailableModules
42+
43+instance FromJSON ListType where
44+ parseJSON = withObject "ListType" $ \o -> do
45+ (listType' :: String) <- o .: "type"
46+ case listType' of
47+ "import" -> do
48+ fp <- o .: "file"
49+ return (Imports fp)
50+ "loadedModules" -> return LoadedModules
51+ "availableModules" -> return AvailableModules
52+ _ -> mzero
53+
54+instance FromJSON Command where
55+ parseJSON = withObject "command" $ \o -> do
56+ (command :: String) <- o .: "command"
57+ case command of
58+ "list" -> do
59+ listType' <- o .:? "params"
60+ return $ List (fromMaybe LoadedModules listType')
61+ "cwd" -> return Cwd
62+ "quit" -> return Quit
63+ "load" -> do
64+ params <- o .: "params"
65+ mods <- params .:? "modules"
66+ deps <- params .:? "dependencies"
67+ return $ Load (fromMaybe [] mods) (fromMaybe [] deps)
68+ "type" -> do
69+ params <- o .: "params"
70+ search <- params .: "search"
71+ filters <- params .: "filters"
72+ return $ Type search filters
73+ "complete" -> do
74+ params <- o .: "params"
75+ filters <- params .:? "filters"
76+ matcher <- params .:? "matcher"
77+ return $ Complete (fromMaybe [] filters) (fromMaybe mempty matcher)
78+ "pursuit" -> do
79+ params <- o .: "params"
80+ query <- params .: "query"
81+ queryType <- params .: "type"
82+ return $ Pursuit query queryType
83+ "caseSplit" -> do
84+ params <- o .: "params"
85+ line <- params .: "line"
86+ begin <- params .: "begin"
87+ end <- params .: "end"
88+ annotations <- params .: "annotations"
89+ type' <- params .: "type"
90+ return $ CaseSplit line begin end (if annotations
91+ then explicitAnnotations
92+ else noAnnotations) type'
93+ "addClause" -> do
94+ params <- o .: "params"
95+ line <- params .: "line"
96+ annotations <- params .: "annotations"
97+ return $ AddClause line (if annotations
98+ then explicitAnnotations
99+ else noAnnotations)
100+ _ -> mzero
101+
src/Language/PureScript/Ide/Completion.hsnew file+35
@@ -0,0 +1,35 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.Completion
3+ (getCompletions, getExactMatches)
4+ where
5+
6+import Prelude ()
7+import Prelude.Compat
8+
9+import Data.Maybe (mapMaybe)
10+import Language.PureScript.Ide.Filter
11+import Language.PureScript.Ide.Matcher
12+import Language.PureScript.Ide.Types
13+
14+-- | Applies the CompletionFilters and the Matcher to the given Modules
15+-- and sorts the found Completions according to the Matching Score
16+getCompletions :: [Filter] -> Matcher -> [Module] -> [Completion]
17+getCompletions filters matcher modules =
18+ runMatcher matcher $ completionsFromModules (applyFilters filters modules)
19+
20+getExactMatches :: DeclIdent -> [Filter] -> [Module] -> [Completion]
21+getExactMatches search filters modules =
22+ completionsFromModules $
23+ applyFilters (equalityFilter search : filters) modules
24+
25+completionsFromModules :: [Module] -> [Completion]
26+completionsFromModules = foldMap completionFromModule
27+ where
28+ completionFromModule :: Module -> [Completion]
29+ completionFromModule (moduleIdent, decls) = mapMaybe (completionFromDecl moduleIdent) decls
30+
31+completionFromDecl :: ModuleIdent -> ExternDecl -> Maybe Completion
32+completionFromDecl mi (FunctionDecl name type') = Just (Completion (mi, name, type'))
33+completionFromDecl mi (DataDecl name kind) = Just (Completion (mi, name, kind))
34+completionFromDecl _ (ModuleDecl name _) = Just (Completion ("module", name, "module"))
35+completionFromDecl _ _ = Nothing
src/Language/PureScript/Ide/Error.hsnew file+43
@@ -0,0 +1,43 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.Error
3+ (ErrorMsg, PscIdeError(..), textError, first)
4+ where
5+
6+import Data.Aeson
7+import Data.Monoid
8+import Data.Text (Text, pack)
9+import Language.PureScript.Ide.Types (ModuleIdent)
10+import qualified Text.Parsec.Error as P
11+
12+type ErrorMsg = String
13+
14+data PscIdeError
15+ = GeneralError ErrorMsg
16+ | NotFound Text
17+ | ModuleNotFound ModuleIdent
18+ | ModuleFileNotFound ModuleIdent
19+ | ParseError P.ParseError ErrorMsg
20+ deriving (Show, Eq)
21+
22+instance ToJSON PscIdeError where
23+ toJSON err = object
24+ [
25+ "resultType" .= ("error" :: Text),
26+ "result" .= textError err
27+ ]
28+
29+textError :: PscIdeError -> Text
30+textError (GeneralError msg) = pack msg
31+textError (NotFound ident) = "Symbol '" <> ident <> "' not found."
32+textError (ModuleNotFound ident) = "Module '" <> ident <> "' not found."
33+textError (ModuleFileNotFound ident) = "Extern file for module " <> ident <>" could not be found"
34+textError (ParseError parseError msg) = pack $ msg <> ": " <> show (escape parseError)
35+ where
36+ -- escape newlines and other special chars so we can send the error over the socket as a single line
37+ escape :: P.ParseError -> String
38+ escape = show
39+
40+-- | Specialized version of `first` from `Data.Bifunctors`
41+first :: (a -> b) -> Either a r -> Either b r
42+first f (Left x) = Left (f x)
43+first _ (Right r2) = Right r2
src/Language/PureScript/Ide/Externs.hsnew file+102
@@ -0,0 +1,102 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE MultiParamTypeClasses #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+{-# LANGUAGE RecordWildCards #-}
5+{-# LANGUAGE ScopedTypeVariables #-}
6+
7+module Language.PureScript.Ide.Externs
8+ (
9+ ExternDecl(..),
10+ ModuleIdent,
11+ DeclIdent,
12+ Type,
13+ Fixity(..),
14+ readExternFile,
15+ convertExterns,
16+ unwrapPositioned,
17+ unwrapPositionedRef
18+ ) where
19+
20+import Prelude ()
21+import Prelude.Compat
22+
23+import Control.Monad.Error.Class
24+import Control.Monad.IO.Class
25+import Data.Maybe (mapMaybe)
26+import Data.Text (Text)
27+import qualified Data.Text as T
28+import qualified Data.Text.IO as T
29+import qualified Language.PureScript.AST.Declarations as D
30+import qualified Language.PureScript.Externs as PE
31+import Language.PureScript.Ide.CodecJSON
32+import Language.PureScript.Ide.Error (PscIdeError (..))
33+import Language.PureScript.Ide.Types
34+import qualified Language.PureScript.Names as N
35+import qualified Language.PureScript.Pretty as PP
36+
37+readExternFile :: (Applicative m, MonadIO m, MonadError PscIdeError m) =>
38+ FilePath -> m PE.ExternsFile
39+readExternFile fp = do
40+ parseResult <- liftIO (decodeT <$> T.readFile fp)
41+ case parseResult of
42+ Nothing -> throwError . GeneralError $ "Parsing the extern at: " ++ fp ++ " failed"
43+ Just externs -> pure externs
44+
45+moduleNameToText :: N.ModuleName -> Text
46+moduleNameToText = T.pack . N.runModuleName
47+
48+properNameToText :: N.ProperName a -> Text
49+properNameToText = T.pack . N.runProperName
50+
51+identToText :: N.Ident -> Text
52+identToText = T.pack . N.runIdent
53+
54+convertExterns :: PE.ExternsFile -> Module
55+convertExterns ef = (moduleName, exportDecls ++ importDecls ++ otherDecls)
56+ where
57+ moduleName = moduleNameToText (PE.efModuleName ef)
58+ importDecls = convertImport <$> PE.efImports ef
59+ exportDecls = mapMaybe (convertExport . unwrapPositionedRef) (PE.efExports ef)
60+ -- Ignoring operator fixities for now since we're not using them
61+ -- operatorDecls = convertOperator <$> PE.efFixities ef
62+ otherDecls = mapMaybe convertDecl (PE.efDeclarations ef)
63+
64+convertImport :: PE.ExternsImport -> ExternDecl
65+convertImport ei = Dependency
66+ (moduleNameToText (PE.eiModule ei))
67+ []
68+ (moduleNameToText <$> PE.eiImportedAs ei)
69+
70+convertExport :: D.DeclarationRef -> Maybe ExternDecl
71+convertExport (D.ModuleRef mn) = Just (Export (moduleNameToText mn))
72+convertExport _ = Nothing
73+
74+convertDecl :: PE.ExternsDeclaration -> Maybe ExternDecl
75+convertDecl PE.EDType{..} = Just $
76+ DataDecl
77+ (properNameToText edTypeName)
78+ (packAndStrip (PP.prettyPrintKind edTypeKind))
79+convertDecl PE.EDTypeSynonym{..} = Just $
80+ DataDecl
81+ (properNameToText edTypeSynonymName)
82+ (packAndStrip (PP.prettyPrintType edTypeSynonymType))
83+convertDecl PE.EDDataConstructor{..} = Just $
84+ DataDecl
85+ (properNameToText edDataCtorName)
86+ (packAndStrip (PP.prettyPrintType edDataCtorType))
87+convertDecl PE.EDValue{..} = Just $
88+ FunctionDecl
89+ (identToText edValueName)
90+ (packAndStrip (PP.prettyPrintType edValueType))
91+convertDecl _ = Nothing
92+
93+packAndStrip :: String -> Text
94+packAndStrip = T.unwords . fmap T.strip . T.lines . T.pack
95+
96+unwrapPositioned :: D.Declaration -> D.Declaration
97+unwrapPositioned (D.PositionedDeclaration _ _ x) = x
98+unwrapPositioned x = x
99+
100+unwrapPositionedRef :: D.DeclarationRef -> D.DeclarationRef
101+unwrapPositionedRef (D.PositionedDeclarationRef _ _ x) = x
102+unwrapPositionedRef x = x
src/Language/PureScript/Ide/Filter.hsnew file+110
@@ -0,0 +1,110 @@
1+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+module Language.PureScript.Ide.Filter
5+ (Filter, moduleFilter, prefixFilter, equalityFilter, dependencyFilter,
6+ runFilter, applyFilters)
7+ where
8+
9+import Prelude ()
10+import Prelude.Compat
11+
12+import Control.Monad
13+import Data.Aeson
14+import Data.Foldable
15+import Data.Maybe (listToMaybe, mapMaybe)
16+import Data.Monoid
17+import Data.Text (Text, isPrefixOf)
18+import Language.PureScript.Ide.Types
19+
20+newtype Filter = Filter (Endo [Module]) deriving(Monoid)
21+
22+mkFilter :: ([Module] -> [Module]) -> Filter
23+mkFilter = Filter . Endo
24+
25+-- | Only keeps the given Modules
26+moduleFilter :: [ModuleIdent] -> Filter
27+moduleFilter =
28+ mkFilter . moduleFilter'
29+
30+moduleFilter' :: [ModuleIdent] -> [Module] -> [Module]
31+moduleFilter' moduleIdents = filter (flip elem moduleIdents . fst)
32+
33+-- | Only keeps the given Modules and all of their dependencies
34+dependencyFilter :: [ModuleIdent] -> Filter
35+dependencyFilter = mkFilter . dependencyFilter'
36+
37+dependencyFilter' :: [ModuleIdent] -> [Module] -> [Module]
38+dependencyFilter' moduleIdents mods =
39+ moduleFilter' (concatMap (getDepForModule mods) moduleIdents) mods
40+ where
41+ getDepForModule :: [Module] -> ModuleIdent -> [ModuleIdent]
42+ getDepForModule ms moduleIdent =
43+ moduleIdent : maybe [] extractDeps (findModule moduleIdent ms)
44+
45+ findModule :: ModuleIdent -> [Module] -> Maybe Module
46+ findModule i ms = listToMaybe $ filter go ms
47+ where go (mn, _) = i == mn
48+
49+ extractDeps :: Module -> [ModuleIdent]
50+ extractDeps = mapMaybe extractDep . snd
51+ where extractDep (Dependency n _ _) = Just n
52+ extractDep _ = Nothing
53+
54+-- | Only keeps Identifiers that start with the given prefix
55+prefixFilter :: Text -> Filter
56+prefixFilter "" = mkFilter id
57+prefixFilter t = mkFilter $ identFilter prefix t
58+ where
59+ prefix :: ExternDecl -> Text -> Bool
60+ prefix (FunctionDecl name _) search = search `isPrefixOf` name
61+ prefix (DataDecl name _) search = search `isPrefixOf` name
62+ prefix (ModuleDecl name _) search = search `isPrefixOf` name
63+ prefix _ _ = False
64+
65+
66+-- | Only keeps Identifiers that are equal to the search string
67+equalityFilter :: Text -> Filter
68+equalityFilter = mkFilter . identFilter equality
69+ where
70+ equality :: ExternDecl -> Text -> Bool
71+ equality (FunctionDecl name _) prefix = prefix == name
72+ equality (DataDecl name _) prefix = prefix == name
73+ equality _ _ = False
74+
75+
76+identFilter :: (ExternDecl -> Text -> Bool ) -> Text -> [Module] -> [Module]
77+identFilter predicate search =
78+ filter (not . null . snd) . fmap filterModuleDecls
79+ where
80+ filterModuleDecls :: Module -> Module
81+ filterModuleDecls (moduleIdent,decls) =
82+ (moduleIdent, filter (`predicate` search) decls)
83+
84+runFilter :: Filter -> [Module] -> [Module]
85+runFilter (Filter f)= appEndo f
86+
87+applyFilters :: [Filter] -> [Module] -> [Module]
88+applyFilters = runFilter . fold
89+
90+instance FromJSON Filter where
91+ parseJSON = withObject "filter" $ \o -> do
92+ (filter' :: String) <- o .: "filter"
93+ case filter' of
94+ "exact" -> do
95+ params <- o .: "params"
96+ search <- params .: "search"
97+ return $ equalityFilter search
98+ "prefix" -> do
99+ params <- o.: "params"
100+ search <- params .: "search"
101+ return $ prefixFilter search
102+ "modules" -> do
103+ params <- o .: "params"
104+ modules <- params .: "modules"
105+ return $ moduleFilter modules
106+ "dependencies" -> do
107+ params <- o .: "params"
108+ deps <- params .: "modules"
109+ return $ dependencyFilter deps
110+ _ -> mzero
src/Language/PureScript/Ide/Matcher.hsnew file+100
@@ -0,0 +1,100 @@
1+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE ScopedTypeVariables #-}
4+module Language.PureScript.Ide.Matcher (Matcher, flexMatcher, runMatcher) where
5+
6+import Prelude ()
7+import Prelude.Compat
8+
9+import Control.Monad
10+import Data.Aeson
11+import Data.Function (on)
12+import Data.List (sortBy)
13+import Data.Maybe (mapMaybe)
14+import Data.Monoid
15+import Data.Text (Text)
16+import qualified Data.Text as T
17+import qualified Data.Text.Encoding as TE
18+import Language.PureScript.Ide.Types
19+import Text.EditDistance
20+import Text.Regex.TDFA ((=~))
21+
22+
23+type ScoredCompletion = (Completion, Double)
24+
25+newtype Matcher = Matcher (Endo [Completion]) deriving(Monoid)
26+
27+instance FromJSON Matcher where
28+ parseJSON = withObject "matcher" $ \o -> do
29+ (matcher :: Maybe String) <- o .:? "matcher"
30+ case matcher of
31+ Just "flex" -> do
32+ params <- o .: "params"
33+ search <- params .: "search"
34+ pure $ flexMatcher search
35+ Just "distance" -> do
36+ params <- o .: "params"
37+ search <- params .: "search"
38+ maxDist <- params .: "maximumDistance"
39+ pure $ distanceMatcher search maxDist
40+ Just _ -> mzero
41+ Nothing -> return mempty
42+
43+-- | Matches any occurence of the search string with intersections
44+-- |
45+-- | The scoring measures how far the matches span the string where
46+-- | closer is better.
47+-- | Examples:
48+-- | flMa matches flexMatcher. Score: 14.28
49+-- | sons matches sortCompletions. Score: 6.25
50+flexMatcher :: Text -> Matcher
51+flexMatcher pattern = mkMatcher (flexMatch pattern)
52+
53+distanceMatcher :: Text -> Int -> Matcher
54+distanceMatcher q maxDist = mkMatcher (distanceMatcher' q maxDist)
55+
56+distanceMatcher' :: Text -> Int -> [Completion] -> [ScoredCompletion]
57+distanceMatcher' q maxDist = mapMaybe go
58+ where
59+ go c@(Completion (_, y, _)) = let d = dist (T.unpack y)
60+ in if d <= maxDist
61+ then Just (c, 1 / fromIntegral d)
62+ else Nothing
63+ dist = levenshteinDistance defaultEditCosts (T.unpack q)
64+
65+mkMatcher :: ([Completion] -> [ScoredCompletion]) -> Matcher
66+mkMatcher matcher = Matcher . Endo $ fmap fst . sortCompletions . matcher
67+
68+runMatcher :: Matcher -> [Completion] -> [Completion]
69+runMatcher (Matcher m)= appEndo m
70+
71+sortCompletions :: [ScoredCompletion] -> [ScoredCompletion]
72+sortCompletions = sortBy (flip compare `on` snd)
73+
74+flexMatch :: Text -> [Completion] -> [ScoredCompletion]
75+flexMatch pattern = mapMaybe (flexRate pattern)
76+
77+flexRate :: Text -> Completion -> Maybe ScoredCompletion
78+flexRate pattern c@(Completion (_,ident,_)) = do
79+ score <- flexScore pattern ident
80+ return (c, score)
81+
82+-- FlexMatching ala Sublime.
83+-- Borrowed from: http://cdewaka.com/2013/06/fuzzy-pattern-matching-in-haskell/
84+--
85+-- By string =~ pattern we'll get the start of the match and the length of
86+-- the matchas a (start, length) tuple if there's a match.
87+-- If match fails then it would be (-1,0)
88+flexScore :: Text -> DeclIdent -> Maybe Double
89+flexScore "" _ = Nothing
90+flexScore pat str =
91+ case TE.encodeUtf8 str =~ TE.encodeUtf8 pat' :: (Int, Int) of
92+ (-1,0) -> Nothing
93+ (start,len) -> Just $ calcScore start (start + len)
94+ where
95+ Just (first,pattern) = T.uncons pat
96+ -- This just interleaves the search string with .*
97+ -- abcd -> a.*b.*c.*d
98+ pat' = first `T.cons` T.concatMap (T.snoc ".*") pattern
99+ calcScore start end =
100+ 100.0 / fromIntegral ((1 + start) * (end - start + 1))
src/Language/PureScript/Ide/Pursuit.hsnew file+65
@@ -0,0 +1,65 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
3+
4+module Language.PureScript.Ide.Pursuit where
5+
6+import Prelude ()
7+import Prelude.Compat
8+
9+import qualified Control.Exception as E
10+import Data.Aeson
11+import Data.ByteString (ByteString)
12+import Data.ByteString.Lazy (fromStrict)
13+import Data.Foldable (toList)
14+import Data.Monoid ((<>))
15+import Data.Maybe (mapMaybe)
16+import Data.String
17+import Data.Text (Text)
18+import qualified Data.Text as T
19+import Language.PureScript.Ide.Types
20+import Network.HTTP.Types.Header (hAccept)
21+import Pipes.HTTP
22+import qualified Pipes.Prelude as P
23+
24+-- We need to remove trailing dots because Pursuit will return a 400 otherwise
25+-- TODO: remove this when the issue is fixed at Pursuit
26+queryPursuit :: Text -> IO ByteString
27+queryPursuit q = do
28+ let qClean = T.dropWhileEnd (== '.') q
29+ req' <- parseUrl "http://pursuit.purescript.org/search"
30+ let req = req'
31+ { queryString=("q=" <> (fromString . T.unpack) qClean)
32+ , requestHeaders=[(hAccept, "application/json")]
33+ }
34+ m <- newManager tlsManagerSettings
35+ withHTTP req m $ \resp ->
36+ P.fold (\x a -> x <> a) "" id $ responseBody resp
37+
38+
39+handler :: HttpException -> IO [a]
40+handler StatusCodeException{} = return []
41+handler _ = return []
42+
43+searchPursuitForDeclarations :: Text -> IO [PursuitResponse]
44+searchPursuitForDeclarations query =
45+ (do r <- queryPursuit query
46+ let results' = decode (fromStrict r) :: Maybe Array
47+ case results' of
48+ Nothing -> pure []
49+ Just results -> pure (mapMaybe isDeclarationResponse (map fromJSON (toList results)))) `E.catch`
50+ handler
51+ where
52+ isDeclarationResponse (Success a@DeclarationResponse{}) = Just a
53+ isDeclarationResponse _ = Nothing
54+
55+findPackagesForModuleIdent :: Text -> IO [PursuitResponse]
56+findPackagesForModuleIdent query =
57+ (do r <- queryPursuit query
58+ let results' = decode (fromStrict r) :: Maybe Array
59+ case results' of
60+ Nothing -> pure []
61+ Just results -> pure (mapMaybe isModuleResponse (map fromJSON (toList results)))) `E.catch`
62+ handler
63+ where
64+ isModuleResponse (Success a@ModuleResponse{}) = Just a
65+ isModuleResponse _ = Nothing
src/Language/PureScript/Ide/Reexports.hsnew file+73
@@ -0,0 +1,73 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE PatternGuards #-}
3+{-# LANGUAGE TupleSections #-}
4+module Language.PureScript.Ide.Reexports where
5+
6+
7+import Prelude ()
8+import Prelude.Compat
9+
10+import Data.List (union)
11+import Data.Map (Map)
12+import qualified Data.Map as Map
13+import Data.Maybe
14+import Language.PureScript.Ide.Types
15+
16+getReexports :: Module -> [ExternDecl]
17+getReexports (mn, decls)= concatMap getExport decls
18+ where getExport d
19+ | (Export mn') <- d
20+ , mn /= mn' = replaceExportWithAliases decls mn'
21+ | otherwise = []
22+
23+dependencyToExport :: ExternDecl -> ExternDecl
24+dependencyToExport (Dependency m _ _) = Export m
25+dependencyToExport decl = decl
26+
27+replaceExportWithAliases :: [ExternDecl] -> ModuleIdent -> [ExternDecl]
28+replaceExportWithAliases decls ident =
29+ case filter isMatch decls of
30+ [] -> [Export ident]
31+ aliases -> map dependencyToExport aliases
32+ where isMatch d
33+ | Dependency _ _ (Just alias) <- d
34+ , alias == ident = True
35+ | otherwise = False
36+
37+replaceReexport :: ExternDecl -> Module -> Module -> Module
38+replaceReexport e@(Export _) (m, decls) (_, newDecls) =
39+ (m, filter (/= e) decls `union` newDecls)
40+replaceReexport _ _ _ = error "Should only get Exports here."
41+
42+emptyModule :: Module
43+emptyModule = ("Empty", [])
44+
45+isExport :: ExternDecl -> Bool
46+isExport (Export _) = True
47+isExport _ = False
48+
49+removeExportDecls :: Module -> Module
50+removeExportDecls = fmap (filter (not . isExport))
51+
52+replaceReexports :: Module -> Map ModuleIdent [ExternDecl] -> Module
53+replaceReexports m db = result
54+ where reexports = getReexports m
55+ result = foldl go (removeExportDecls m) reexports
56+
57+ go :: Module -> ExternDecl -> Module
58+ go m' re@(Export name) = replaceReexport re m' (getModule name)
59+ go _ _ = error "partiality! woohoo"
60+
61+ getModule :: ModuleIdent -> Module
62+ getModule name = clean res
63+ where res = fromMaybe emptyModule $ (name , ) <$> Map.lookup name db
64+ -- we have to do this because keeping self exports in will result in
65+ -- infinite loops
66+ clean (mn, decls) = (mn,) (filter (/= Export mn) decls)
67+
68+resolveReexports :: Map ModuleIdent [ExternDecl] -> Module -> Module
69+resolveReexports modules m = do
70+ let replaced = replaceReexports m modules
71+ if null . getReexports $ replaced
72+ then replaced
73+ else resolveReexports modules replaced
src/Language/PureScript/Ide/SourceFile.hsnew file+106
@@ -0,0 +1,106 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+{-# LANGUAGE MultiParamTypeClasses #-}
3+{-# LANGUAGE OverloadedStrings #-}
4+module Language.PureScript.Ide.SourceFile where
5+
6+import Prelude ()
7+import Prelude.Compat
8+
9+import Control.Monad.Error.Class
10+import Control.Monad.IO.Class
11+import Control.Monad.Trans.Except
12+import Data.Maybe (mapMaybe)
13+import Data.Monoid
14+import qualified Data.Text as T
15+import qualified Language.PureScript.AST.Declarations as D
16+import qualified Language.PureScript.AST.SourcePos as SP
17+import Language.PureScript.Ide.Error
18+import Language.PureScript.Ide.Externs (unwrapPositioned,
19+ unwrapPositionedRef)
20+import Language.PureScript.Ide.Types
21+import qualified Language.PureScript.Names as N
22+import qualified Language.PureScript.Parser as P
23+import System.Directory
24+
25+parseModuleFromFile :: (Applicative m, MonadIO m, MonadError PscIdeError m) =>
26+ FilePath -> m D.Module
27+parseModuleFromFile fp = do
28+ exists <- liftIO (doesFileExist fp)
29+ if exists
30+ then do
31+ content <- liftIO (readFile fp)
32+ let m = do tokens <- P.lex fp content
33+ P.runTokenParser "" P.parseModule tokens
34+ either (throwError . (`ParseError` "File could not be parsed.")) pure m
35+ else throwError (NotFound "File does not exist.")
36+
37+-- data Module = Module SourceSpan [Comment] ModuleName [Declaration] (Maybe [DeclarationRef])
38+
39+getDeclarations :: D.Module -> [D.Declaration]
40+getDeclarations (D.Module _ _ _ declarations _) = declarations
41+
42+getImports :: D.Module -> [D.Declaration]
43+getImports (D.Module _ _ _ declarations _) =
44+ mapMaybe isImport declarations
45+ where
46+ isImport (D.PositionedDeclaration _ _ (i@D.ImportDeclaration{})) = Just i
47+ isImport _ = Nothing
48+
49+getImportsForFile :: (Applicative m, MonadIO m, MonadError PscIdeError m) =>
50+ FilePath -> m [ModuleImport]
51+getImportsForFile fp = do
52+ module' <- parseModuleFromFile fp
53+ let imports = getImports module'
54+ pure (mkModuleImport . unwrapPositionedImport <$> imports)
55+ where mkModuleImport (D.ImportDeclaration mn importType' qualifier _) =
56+ ModuleImport
57+ (T.pack (N.runModuleName mn))
58+ importType'
59+ (T.pack . N.runModuleName <$> qualifier)
60+ mkModuleImport _ = error "Shouldn't have gotten anything but Imports here"
61+ unwrapPositionedImport (D.ImportDeclaration mn importType' qualifier b) =
62+ D.ImportDeclaration mn (unwrapImportType importType') qualifier b
63+ unwrapPositionedImport x = x
64+ unwrapImportType (D.Explicit decls) = D.Explicit (map unwrapPositionedRef decls)
65+ unwrapImportType (D.Hiding decls) = D.Hiding (map unwrapPositionedRef decls)
66+ unwrapImportType D.Implicit = D.Implicit
67+
68+getPositionedImports :: D.Module -> [D.Declaration]
69+getPositionedImports (D.Module _ _ _ declarations _) =
70+ mapMaybe isImport declarations
71+ where
72+ isImport i@(D.PositionedDeclaration _ _ (D.ImportDeclaration{})) = Just i
73+ isImport _ = Nothing
74+
75+getDeclPosition :: D.Module -> String -> Maybe SP.SourceSpan
76+getDeclPosition m ident =
77+ let decls = getDeclarations m
78+ in getFirst (foldMap (match ident) decls)
79+ where match q (D.PositionedDeclaration ss _ decl) = First (if go q decl
80+ then Just ss
81+ else Nothing)
82+ match _ _ = First Nothing
83+
84+ go q (D.DataDeclaration _ name _ constructors) =
85+ properEqual name q || any (\(x,_) -> properEqual x q) constructors
86+ go q (D.DataBindingGroupDeclaration decls) = any (go q) decls
87+ go q (D.TypeSynonymDeclaration name _ _) = properEqual name q
88+ go q (D.TypeDeclaration ident' _) = identEqual ident' q
89+ go q (D.ValueDeclaration ident' _ _ _) = identEqual ident' q
90+ go q (D.ExternDeclaration ident' _) = identEqual ident' q
91+ go q (D.ExternDataDeclaration name _) = properEqual name q
92+ go q (D.TypeClassDeclaration name _ _ members) =
93+ properEqual name q || any (go q . unwrapPositioned) members
94+ go q (D.TypeInstanceDeclaration ident' _ _ _ _) =
95+ identEqual ident' q
96+ go _ _ = False
97+
98+ properEqual x q = N.runProperName x == q
99+ identEqual x q = N.runIdent x == q
100+
101+goToDefinition :: String -> FilePath -> IO (Maybe SP.SourceSpan)
102+goToDefinition q fp = do
103+ m <- runExceptT (parseModuleFromFile fp)
104+ case m of
105+ Right module' -> return $ getDeclPosition module' q
106+ Left _ -> return Nothing
src/Language/PureScript/Ide/State.hsnew file+80
@@ -0,0 +1,80 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE MultiParamTypeClasses #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE PackageImports #-}
6+{-# LANGUAGE TemplateHaskell #-}
7+{-# LANGUAGE TupleSections #-}
8+
9+module Language.PureScript.Ide.State where
10+
11+import Prelude ()
12+import Prelude.Compat
13+
14+import Control.Concurrent.STM
15+import Control.Monad.IO.Class
16+import "monad-logger" Control.Monad.Logger
17+import Control.Monad.Reader.Class
18+import qualified Data.Map.Lazy as M
19+import Data.Maybe (catMaybes)
20+import Data.Monoid
21+import qualified Data.Text as T
22+import Language.PureScript.Externs
23+import Language.PureScript.Ide.Externs
24+import Language.PureScript.Ide.Reexports
25+import Language.PureScript.Ide.Types
26+import Language.PureScript.Names
27+
28+getPscIdeState :: (PscIde m, Functor m) =>
29+ m (M.Map ModuleIdent [ExternDecl])
30+getPscIdeState = do
31+ stateVar <- envStateVar <$> ask
32+ liftIO $ pscStateModules <$> readTVarIO stateVar
33+
34+getExternFiles :: (PscIde m, Functor m) =>
35+ m (M.Map ModuleName ExternsFile)
36+getExternFiles = do
37+ stateVar <- envStateVar <$> ask
38+ liftIO (externsFiles <$> readTVarIO stateVar)
39+
40+getAllDecls :: (PscIde m, Functor m) => m [ExternDecl]
41+getAllDecls = concat <$> getPscIdeState
42+
43+getAllModules :: (PscIde m, Functor m) => m [Module]
44+getAllModules = M.toList <$> getPscIdeState
45+
46+getAllModulesWithReexports :: (PscIde m, MonadLogger m, Applicative m) =>
47+ m [Module]
48+getAllModulesWithReexports = do
49+ mis <- M.keys <$> getPscIdeState
50+ ms <- traverse getModuleWithReexports mis
51+ pure (catMaybes ms)
52+
53+getModule :: (PscIde m, MonadLogger m, Applicative m) =>
54+ ModuleIdent -> m (Maybe Module)
55+getModule m = do
56+ modules <- getPscIdeState
57+ pure ((m,) <$> M.lookup m modules)
58+
59+getModuleWithReexports :: (PscIde m, MonadLogger m, Applicative m) =>
60+ ModuleIdent -> m (Maybe Module)
61+getModuleWithReexports mi = do
62+ m <- getModule mi
63+ modules <- getPscIdeState
64+ pure $ resolveReexports modules <$> m
65+
66+insertModule ::(PscIde m, MonadLogger m) =>
67+ ExternsFile -> m ()
68+insertModule externsFile = do
69+ env <- ask
70+ let moduleName = efModuleName externsFile
71+ $(logDebug) $ "Inserting Module: " <> (T.pack (runModuleName moduleName))
72+ liftIO . atomically $ insertModule' (envStateVar env) externsFile
73+
74+insertModule' :: TVar PscIdeState -> ExternsFile -> STM ()
75+insertModule' st ef = do
76+ modifyTVar (st) $ \x ->
77+ x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
78+ , pscStateModules = let (mn, decls ) = convertExterns ef
79+ in M.insert mn decls (pscStateModules x)
80+ }
src/Language/PureScript/Ide/Types.hsnew file+240
@@ -0,0 +1,240 @@
1+{-# LANGUAGE ConstraintKinds #-}
2+{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
4+{-# LANGUAGE OverloadedStrings #-}
5+{-# LANGUAGE RecordWildCards #-}
6+{-# LANGUAGE ScopedTypeVariables #-}
7+
8+module Language.PureScript.Ide.Types where
9+
10+import Prelude ()
11+import Prelude.Compat
12+
13+import Control.Concurrent.STM
14+import Control.Monad
15+import Control.Monad.Reader.Class
16+import Control.Monad.Trans
17+import Data.Aeson
18+import Data.Map.Lazy as M
19+import Data.Maybe (maybeToList)
20+import Data.Text (Text (), pack, unpack)
21+import qualified Language.PureScript.AST.Declarations as D
22+import Language.PureScript.Externs
23+import Language.PureScript.Names
24+import qualified Language.PureScript.Names as N
25+
26+import Text.Parsec
27+import Text.Parsec.Text
28+
29+type ModuleIdent = Text
30+type DeclIdent = Text
31+type Type = Text
32+
33+data Fixity = Infix | Infixl | Infixr deriving(Show, Eq, Ord)
34+
35+data ExternDecl
36+ = FunctionDecl { functionName :: DeclIdent
37+ , functionType :: Type
38+ }
39+ | FixityDeclaration Fixity
40+ Int
41+ DeclIdent
42+ | Dependency { dependencyModule :: ModuleIdent
43+ , dependencyNames :: [Text]
44+ , dependencyAlias :: Maybe Text
45+ }
46+ | ModuleDecl ModuleIdent
47+ [DeclIdent]
48+ | DataDecl DeclIdent
49+ Text
50+ | Export ModuleIdent
51+ deriving (Show,Eq,Ord)
52+
53+instance ToJSON ExternDecl where
54+ toJSON (FunctionDecl n t) = object ["name" .= n, "type" .= t]
55+ toJSON (ModuleDecl n t) = object ["name" .= n, "type" .= t]
56+ toJSON (DataDecl n t) = object ["name" .= n, "type" .= t]
57+ toJSON (Dependency n names _) = object ["module" .= n, "names" .= names]
58+ toJSON (FixityDeclaration f p n) = object ["name" .= n
59+ , "fixity" .= show f
60+ , "precedence" .= p]
61+ toJSON (Export _) = object []
62+
63+type Module = (ModuleIdent, [ExternDecl])
64+
65+data Configuration =
66+ Configuration {
67+ confOutputPath :: FilePath
68+ , confDebug :: Bool
69+ }
70+
71+data PscIdeEnvironment =
72+ PscIdeEnvironment {
73+ envStateVar :: TVar PscIdeState
74+ , envConfiguration :: Configuration
75+ }
76+
77+type PscIde m = (Applicative m, MonadIO m, MonadReader PscIdeEnvironment m)
78+
79+data PscIdeState =
80+ PscIdeState {
81+ pscStateModules :: M.Map Text [ExternDecl]
82+ , externsFiles :: M.Map ModuleName ExternsFile
83+ } deriving Show
84+
85+emptyPscIdeState :: PscIdeState
86+emptyPscIdeState = PscIdeState M.empty M.empty
87+
88+newtype Completion =
89+ Completion (ModuleIdent, DeclIdent, Type)
90+ deriving (Show,Eq)
91+
92+data ModuleImport =
93+ ModuleImport {
94+ importModuleName :: ModuleIdent
95+ , importType :: D.ImportDeclarationType
96+ , importQualifier :: Maybe Text
97+ } deriving(Show)
98+
99+instance Eq ModuleImport where
100+ mi1 == mi2 = importModuleName mi1 == importModuleName mi2
101+ && importQualifier mi1 == importQualifier mi2
102+
103+instance ToJSON ModuleImport where
104+ toJSON (ModuleImport mn D.Implicit qualifier) =
105+ object $ ["module" .= mn
106+ , "importType" .= ("implicit" :: Text)
107+ ] ++ fmap (\x -> "qualifier" .= x) (maybeToList qualifier)
108+ toJSON (ModuleImport mn (D.Explicit refs) _) =
109+ object ["module" .= mn
110+ , "importType" .= ("explicit" :: Text)
111+ , "identifiers" .= (identifierFromDeclarationRef <$> refs)]
112+ toJSON (ModuleImport mn (D.Hiding refs) _) =
113+ object ["module" .= mn
114+ , "importType" .= ("hiding" :: Text)
115+ , "identifiers" .= (identifierFromDeclarationRef <$> refs)]
116+
117+identifierFromDeclarationRef :: D.DeclarationRef -> String
118+identifierFromDeclarationRef (D.TypeRef name _) = N.runProperName name
119+identifierFromDeclarationRef (D.ValueRef ident) = N.runIdent ident
120+identifierFromDeclarationRef (D.TypeClassRef name) = N.runProperName name
121+identifierFromDeclarationRef _ = ""
122+
123+instance FromJSON Completion where
124+ parseJSON (Object o) = do
125+ m <- o .: "module"
126+ d <- o .: "identifier"
127+ t <- o .: "type"
128+ return $ Completion (m, d, t)
129+ parseJSON _ = mzero
130+
131+instance ToJSON Completion where
132+ toJSON (Completion (m,d,t)) =
133+ object ["module" .= m, "identifier" .= d, "type" .= t]
134+
135+data Success =
136+ CompletionResult [Completion]
137+ | TextResult Text
138+ | MultilineTextResult [Text]
139+ | PursuitResult [PursuitResponse]
140+ | ImportList [ModuleImport]
141+ | ModuleList [ModuleIdent]
142+ deriving(Show, Eq)
143+
144+encodeSuccess :: (ToJSON a) => a -> Value
145+encodeSuccess res =
146+ object ["resultType" .= ("success" :: Text), "result" .= res]
147+
148+instance ToJSON Success where
149+ toJSON (CompletionResult cs) = encodeSuccess cs
150+ toJSON (TextResult t) = encodeSuccess t
151+ toJSON (MultilineTextResult ts) = encodeSuccess ts
152+ toJSON (PursuitResult resp) = encodeSuccess resp
153+ toJSON (ImportList decls) = encodeSuccess decls
154+ toJSON (ModuleList modules) = encodeSuccess modules
155+
156+newtype PursuitQuery = PursuitQuery Text
157+ deriving (Show, Eq)
158+
159+data PursuitSearchType = Package | Identifier
160+ deriving (Show, Eq)
161+
162+instance FromJSON PursuitSearchType where
163+ parseJSON (String t) = case t of
164+ "package" -> return Package
165+ "completion" -> return Identifier
166+ _ -> mzero
167+ parseJSON _ = mzero
168+
169+instance FromJSON PursuitQuery where
170+ parseJSON o = fmap PursuitQuery (parseJSON o)
171+
172+data PursuitResponse
173+ = ModuleResponse { moduleResponseName :: Text
174+ , moduleResponsePackage :: Text}
175+ | DeclarationResponse { declarationResponseType :: Text
176+ , declarationResponseModule :: Text
177+ , declarationResponseIdent :: Text
178+ , declarationResponsePackage :: Text
179+ }
180+ deriving (Show,Eq)
181+
182+instance FromJSON PursuitResponse where
183+ parseJSON (Object o) = do
184+ package <- o .: "package"
185+ info <- o .: "info"
186+ (type' :: String) <- info .: "type"
187+ case type' of
188+ "module" -> do
189+ name <- info .: "module"
190+ return
191+ ModuleResponse
192+ { moduleResponseName = name
193+ , moduleResponsePackage = package
194+ }
195+ "declaration" -> do
196+ moduleName <- info .: "module"
197+ Right (ident, declType) <- typeParse <$> o .: "text"
198+ return
199+ DeclarationResponse
200+ { declarationResponseType = declType
201+ , declarationResponseModule = moduleName
202+ , declarationResponseIdent = ident
203+ , declarationResponsePackage = package
204+ }
205+ _ -> mzero
206+ parseJSON _ = mzero
207+
208+
209+typeParse :: Text -> Either Text (Text, Text)
210+typeParse t = case parse parseType "" t of
211+ Right (x,y) -> Right (pack x, pack y)
212+ Left err -> Left (pack (show err))
213+ where
214+ parseType :: Parser (String, String)
215+ parseType = do
216+ name <- identifier
217+ _ <- string "::"
218+ spaces
219+ type' <- many1 anyChar
220+ return (unpack name, type')
221+
222+identifier :: Parser Text
223+identifier = do
224+ spaces
225+ ident <-
226+ -- necessary for being able to parse the following ((++), concat)
227+ between (char '(') (char ')') (many1 (noneOf ", )")) <|>
228+ many1 (noneOf ", )")
229+ spaces
230+ return (pack ident)
231+
232+instance ToJSON PursuitResponse where
233+ toJSON ModuleResponse{..} =
234+ object ["module" .= moduleResponseName, "package" .= moduleResponsePackage]
235+ toJSON DeclarationResponse{..} =
236+ object
237+ [ "module" .= declarationResponseModule
238+ , "ident" .= declarationResponseIdent
239+ , "type" .= declarationResponseType
240+ , "package" .= declarationResponsePackage]
src/Language/PureScript/Ide/Watcher.hsnew file+40
@@ -0,0 +1,40 @@
1+{-# LANGUAGE RecordWildCards #-}
2+module Language.PureScript.Ide.Watcher where
3+
4+import Prelude ()
5+import Prelude.Compat
6+
7+import Control.Concurrent (threadDelay)
8+import Control.Concurrent.STM
9+import Control.Monad
10+import Control.Monad.Trans.Except
11+import qualified Data.Map as M
12+import Data.Maybe (isJust)
13+import Language.PureScript.Externs
14+import Language.PureScript.Ide.Externs
15+import Language.PureScript.Ide.State
16+import Language.PureScript.Ide.Types
17+import System.FilePath
18+import System.FSNotify
19+
20+
21+reloadFile :: TVar PscIdeState -> FilePath -> IO ()
22+reloadFile stateVar fp = do
23+ (Right ef@ExternsFile{..}) <- runExceptT $ readExternFile fp
24+ reloaded <- atomically $ do
25+ st <- readTVar stateVar
26+ if isLoaded efModuleName st
27+ then
28+ insertModule' stateVar ef *> pure True
29+ else
30+ pure False
31+ when reloaded $ putStrLn $ "Reloaded File at: " ++ fp
32+ where
33+ isLoaded name st = isJust (M.lookup name (externsFiles st))
34+
35+watcher :: TVar PscIdeState -> FilePath -> IO ()
36+watcher stateVar fp = withManager $ \mgr -> do
37+ _ <- watchTree mgr fp
38+ (\ev -> takeFileName (eventPath ev) == "externs.json")
39+ (reloadFile stateVar . eventPath)
40+ forever (threadDelay 10000)
stack-lts-2.yaml+1
@@ -7,4 +7,5 @@ extra-deps:
7- boxes-0.1.4
7- boxes-0.1.4
8- pattern-arrows-0.0.2
8- pattern-arrows-0.0.2
9- sourcemap-0.1.6
9- sourcemap-0.1.6
10+- fsnotify-0.2.1
10resolver: lts-2.22
11resolver: lts-2.22
tests/Language/PureScript/Ide/FilterSpec.hsnew file+59
@@ -0,0 +1,59 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.FilterSpec where
3+
4+import Data.Text (Text)
5+import Language.PureScript.Ide.Filter
6+import Language.PureScript.Ide.Types
7+import Test.Hspec
8+
9+modules :: [Module]
10+modules =
11+ [
12+ ("Module.A", [FunctionDecl "function1" ""]),
13+ ("Module.B", [DataDecl "data1" ""]),
14+ ("Module.C", [ModuleDecl "Module.C" []]),
15+ ("Module.D", [Dependency "Module.C" [] Nothing, FunctionDecl "asd" ""])
16+ ]
17+
18+runEq :: Text -> [Module]
19+runEq s = runFilter (equalityFilter s) modules
20+runPrefix :: Text -> [Module]
21+runPrefix s = runFilter (prefixFilter s) modules
22+runModule :: [ModuleIdent] -> [Module]
23+runModule ms = runFilter (moduleFilter ms) modules
24+runDependency :: [ModuleIdent] -> [Module]
25+runDependency ms = runFilter (dependencyFilter ms) modules
26+
27+spec :: Spec
28+spec = do
29+ describe "equality Filter" $ do
30+ it "removes empty modules" $
31+ runEq "test" `shouldBe` []
32+ it "keeps function declarations that are equal" $
33+ runEq "function1" `shouldBe` [head modules]
34+ -- TODO: It would be more sensible to match Constructors
35+ it "keeps data declarations that are equal" $
36+ runEq "data1" `shouldBe` [modules !! 1]
37+ describe "prefixFilter" $ do
38+ it "keeps everything on empty string" $
39+ runPrefix "" `shouldBe` modules
40+ it "keeps functionname prefix matches" $
41+ runPrefix "fun" `shouldBe` [head modules]
42+ it "keeps data decls prefix matches" $
43+ runPrefix "dat" `shouldBe` [modules !! 1]
44+ it "keeps module decl prefix matches" $
45+ runPrefix "Mod" `shouldBe` [modules !! 2]
46+ describe "moduleFilter" $ do
47+ it "removes everything on empty input" $
48+ runModule [] `shouldBe` []
49+ it "only keeps the specified modules" $
50+ runModule ["Module.A", "Module.C"] `shouldBe` [head modules, modules !! 2]
51+ it "ignores modules that are not in scope" $
52+ runModule ["Module.A", "Module.C", "Unknown"] `shouldBe` [head modules, modules !! 2]
53+ describe "dependencyFilter" $ do
54+ it "removes everything on empty input" $
55+ runDependency [] `shouldBe` []
56+ it "only keeps the specified modules if they have no imports" $
57+ runDependency ["Module.A", "Module.B"] `shouldBe` [head modules, modules !! 1]
58+ it "keeps the specified modules and their imports" $
59+ runDependency ["Module.A", "Module.D"] `shouldBe` [head modules, modules !! 2, modules !! 3]
tests/Language/PureScript/Ide/MatcherSpec.hsnew file+31
@@ -0,0 +1,31 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module Language.PureScript.Ide.MatcherSpec where
4+
5+import Data.Text (Text)
6+import Language.PureScript.Ide.Matcher
7+import Language.PureScript.Ide.Types
8+import Test.Hspec
9+
10+completions :: [Completion]
11+completions = [
12+ Completion ("", "firstResult", ""),
13+ Completion ("", "secondResult", ""),
14+ Completion ("", "fiult", "")
15+ ]
16+
17+mkResult :: [Int] -> [Completion]
18+mkResult = map (completions !!)
19+
20+runFlex :: Text -> [Completion]
21+runFlex s = runMatcher (flexMatcher s) completions
22+
23+spec :: Spec
24+spec = do
25+ describe "Flex Matcher" $ do
26+ it "doesn't match on an empty string" $
27+ runFlex "" `shouldBe` []
28+ it "matches on equality" $
29+ runFlex "firstResult" `shouldBe` mkResult [0]
30+ it "scores short matches higher and sorts accordingly" $
31+ runFlex "filt" `shouldBe` mkResult [2, 0]
tests/Language/PureScript/Ide/ReexportsSpec.hsnew file+77
@@ -0,0 +1,77 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.ReexportsSpec where
3+
4+import Control.Exception (evaluate)
5+import Data.List (sort)
6+import qualified Data.Map as Map
7+import Language.PureScript.Ide.Reexports
8+import Language.PureScript.Ide.Types
9+import Test.Hspec
10+
11+decl1 :: ExternDecl
12+decl1 = FunctionDecl "filter" "asdasd"
13+decl2 :: ExternDecl
14+decl2 = DataDecl "Tree" "* -> *"
15+decl3 :: ExternDecl
16+decl3 = DataDecl "TreeAsd" "* -> *"
17+dep1 :: ExternDecl
18+dep1 = Dependency "Test.Foo" [] (Just "T")
19+dep2 :: ExternDecl
20+dep2 = Dependency "Test.Bar" [] (Just "T")
21+
22+circularModule :: Module
23+circularModule = ("Circular", [Export "Circular"])
24+
25+module1 :: Module
26+module1 = ("Module1", [Export "Module2", Export "Module3", decl1])
27+
28+module2 :: Module
29+module2 = ("Module2", [decl2])
30+
31+module3 :: Module
32+module3 = ("Module3", [decl3])
33+
34+module4 :: Module
35+module4 = ("Module4", [Export "T", decl1, dep1, dep2])
36+
37+result :: Module
38+result = ("Module1", [decl1, decl2, Export "Module3"])
39+
40+db :: Map.Map ModuleIdent [ExternDecl]
41+db = Map.fromList [module1, module2, module3]
42+
43+shouldBeEqualSorted :: Module -> Module -> Expectation
44+shouldBeEqualSorted (n1, d1) (n2, d2) = (n1, sort d1) `shouldBe` (n2, sort d2)
45+
46+spec :: Spec
47+spec = do
48+ describe "Reexports" $ do
49+ it "finds all reexports" $
50+ getReexports module1 `shouldBe` [Export "Module2", Export "Module3"]
51+
52+ it "replaces a reexport with another module" $
53+ replaceReexport (Export "Module2") module1 module2 `shouldBeEqualSorted` result
54+
55+ it "adds another module even if there is no export statement" $
56+ replaceReexport (Export "Module2") ("Module1", [decl1, Export "Module3"]) module2
57+ `shouldBeEqualSorted` result
58+
59+ it "only adds a declaration once" $
60+ let replaced = replaceReexport (Export "Module2") module1 module2
61+ in replaceReexport (Export "Module2") replaced module2 `shouldBeEqualSorted` result
62+
63+ it "should error when given a non-Export to replace" $
64+ evaluate (replaceReexport decl1 module1 module2)
65+ `shouldThrow` errorCall "Should only get Exports here."
66+ it "replaces all Exports with their corresponding declarations" $
67+ replaceReexports module1 db `shouldBe` ("Module1", [decl1, decl2, decl3])
68+
69+ it "does not list itself as a reexport" $
70+ getReexports circularModule `shouldBe` []
71+
72+ it "does not include circular references when replacing reexports" $
73+ replaceReexports circularModule (uncurry Map.singleton circularModule )
74+ `shouldBe` ("Circular", [])
75+
76+ it "replaces exported aliases with imported module" $
77+ getReexports module4 `shouldBe` [Export "Test.Foo", Export "Test.Bar"]
tests/Language/PureScript/IdeSpec.hsnew file+35
@@ -0,0 +1,35 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE PackageImports #-}
3+module Language.PureScript.IdeSpec where
4+
5+import Control.Concurrent.STM
6+import Control.Monad.Reader
7+import Data.List
8+import qualified Data.Map as Map
9+import Language.PureScript.Ide
10+import Language.PureScript.Ide.Types
11+import Test.Hspec
12+
13+testState :: PscIdeState
14+testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) (Map.empty)
15+
16+defaultConfig :: Configuration
17+defaultConfig =
18+ Configuration
19+ {
20+ confOutputPath = "output/"
21+ , confDebug = False
22+ }
23+
24+spec :: SpecWith ()
25+spec = do
26+ describe "list" $ do
27+ describe "loadedModules" $ do
28+ it "returns an empty list when no modules are loaded" $ do
29+ st <- newTVarIO emptyPscIdeState
30+ result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
31+ result `shouldBe` ModuleList []
32+ it "returns the list of loaded modules" $ do
33+ st <- newTVarIO testState
34+ ModuleList result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
35+ sort result `shouldBe` sort ["Data.Array", "Control.Monad.Eff"]
tests/Main.hs+3
@@ -25,6 +25,7 @@ import qualified TestCompiler
25import qualified TestPscPublish
25import qualified TestPscPublish
26import qualified TestDocs
26import qualified TestDocs
27import qualified TestPsci
27import qualified TestPsci
28+import qualified TestPscIde
28
29
29main :: IO ()
30main :: IO ()
30main = do
31main = do
@@ -36,6 +37,8 @@ main = do
36 TestPscPublish.main
37 TestPscPublish.main
37 heading "psci test suite"
38 heading "psci test suite"
38 TestPsci.main
39 TestPsci.main
40+ heading "psc-ide test suite"
41+ TestPscIde.main
39
42
40 where
43 where
41 heading msg = do
44 heading msg = do
tests/PscIdeSpec.hsnew file+1
@@ -0,0 +1 @@
1+{-# OPTIONS_GHC -F -pgmF hspec-discover -optF --module-name=PscIdeSpec #-}
tests/TestPscIde.hsnew file+7
@@ -0,0 +1,7 @@
1+module TestPscIde where
2+
3+import qualified PscIdeSpec
4+import Test.Hspec
5+
6+main :: IO ()
7+main = hspec PscIdeSpec.spec