git.iko.soy/rowtype-yoga/purescript-native
on golang help sign in
[psc-ide] - Import helper commands

* Adds import parsing and adding an implicit import * addImport statement this will search the loaded modules for the given identifier and add a simple Value import for now * reply with Completions rather then just the ModuleName * Adds tests to cover some of the functionality * fix warnings * Set up integration tests for psc-ide * try to put the compiled binaries on travis's PATH * fix warnings and give the server more startup time * allow specifying an output file * allow specifying the module to load * tests writing out changed imports to temp files * addImplicitImport command for Integration tests * more tests * fix warnings and try to put things on the path * don't clutter the main .gitignore * documents new commands * actually use the supplied filters also does some cleanup * formatting and documentation * adds support files to cabal file * make typesynonym import work The current internal format generate two seperate entries for typesynonyms so we need to flatten the list of matches by modulename * adds documentation and shortens parseImport * reduce duplication in Language.PureScript.Errors * Adding bsermons as contributor. * fix typo in module header * apply span suggestion (Thanks that looks better) * work around special case in prettyPrintImport also adds a test to cover this case * refactors import tests also adds a testcase for when an identifier to import cannot be found * Try harder to correctly detect the import section * More robust parsing by using the moduleParser on the entire importsection at once * keeps trailing newlines or comments inside the modulebody * don't duplicate refs in explicit imports * make a selfreferencing import a noop Don't add an import for an identifier defined in the module itself tiny bit of refactoring * add a testcase for the selfreferencing import case * adds a loadAll command to psc-ide This command loads every compiled module it detects inside the output folder. This command is somewhat of a necessity to make the import lookup useful. We can now start the server, load all the compiled modules and have import completion for all the modules in our project. Previously we'd have to either load a file that already imported a certain module or we had to scan the output folder ourself. * remove superfluous instances, fix cabal warnings * drag the ExternDecl through the Matcher * extend ExternDecl type * fix warnings and clean up a bit * remove duplicate entries when inserting externs Typeclasses generated 3 entries: 1. The TypeclassDeclaration 2. A TypeDeclaration 3. A TypesynonymDeclaration The last two also applied to TypeDeclarations * turn the typeclass test "on" add dtor test * use the "class" syntax when importing typeclasses also look up the type belonging to a dataconstructor and import all types with an open dataconstructor list like: import Data.Maybe (Maybe(..)) * always imports newtypes as dataconstructors The same goes for data declarations with matching constructor and type names. In the case of matching typename and constructorname but different types we just tell the user we can't decide for now. * test the new behaviour * add testcases for dataconstructor imports * move text json encoding into util * handle dataconstructor imports * turn ProperRef into TypeRef this workaround shouldn't be necessary after typeclass import syntax is unambiguous from type import * bracket operators in imports * start a new importsection if there are no imports * make completionFromMatch total * don't add an explicit import for an already implicit one * add type synonym declaration do not raise undecidable error for parameterized type declarations. * preserve the order of implicit imports This makes sure we don't change the set of warnings for implicit imports * introduce an Ord instance for imports This rates qualified Implicit imports greater than non-qualified ones. This way the first Implicit unqualified import appears at the top when sorting. * formatting * rebasing * revert to master

commit0d78fc1cf1cf2d8af34530761f8d29efacd311de authorChristoph Hegemann <kRITZCREEK@users.noreply.github.com> dateWed 6 Apr 2016, 05:06 (11 years ago) parent90a755f
33 files changed+1584−275
.travis.yml+3 CONTRIBUTORS.md+1 psc-ide-server/Main.hs+16−1 psc-ide-server/PROTOCOL.md+95−3 purescript.cabal+18−4 src/Language/PureScript/Ide.hs+63−21 src/Language/PureScript/Ide/CaseSplit.hs+47−38 src/Language/PureScript/Ide/CodecJSON.hs−13 src/Language/PureScript/Ide/Command.hs+72−20 src/Language/PureScript/Ide/Completion.hs+7−10 src/Language/PureScript/Ide/Error.hs+15−6 src/Language/PureScript/Ide/Externs.hs+58−46 src/Language/PureScript/Ide/Filter.hs+28−11 src/Language/PureScript/Ide/Imports.hs+355 src/Language/PureScript/Ide/Matcher.hs+46−25 src/Language/PureScript/Ide/Pursuit.hs+14 src/Language/PureScript/Ide/Reexports.hs+16 src/Language/PureScript/Ide/SourceFile.hs+16−1 src/Language/PureScript/Ide/State.hs+21−6 src/Language/PureScript/Ide/Types.hs+46−48 src/Language/PureScript/Ide/Util.hs+65 src/Language/PureScript/Ide/Watcher.hs+15 tests/Language/PureScript/Ide/FilterSpec.hs+7−3 tests/Language/PureScript/Ide/Imports/IntegrationSpec.hs+101 tests/Language/PureScript/Ide/ImportsSpec.hs+125 tests/Language/PureScript/Ide/Integration.hs+238 tests/Language/PureScript/Ide/MatcherSpec.hs+32−7 tests/Language/PureScript/Ide/ReexportsSpec.hs+5−4 tests/Language/PureScript/IdeSpec.hs+8−8 tests/support/pscide/.gitignore+7 tests/support/pscide/src/ImportsSpec.purs+5 tests/support/pscide/src/ImportsSpec1.purs+32 tests/support/pscide/src/Main.purs+7
.travis.yml+3
@@ -19,6 +19,9 @@ before_install:
1919 - export PATH="$HOME/.cabal/bin:$PATH"
2020 - export PATH="/opt/happy/1.19.5/bin:/$PATH"
2121 - export PATH="/opt/alex/3.1.4/bin:/$PATH"
22+ - export PATH="$HOME/build/purescript/purescript/dist/build/psc:/$PATH"
23+ - export PATH="$HOME/build/purescript/purescript/dist/build/psc-ide-server:/$PATH"
24+ - export PATH="$HOME/build/purescript/purescript/dist/build/psc-ide-client:/$PATH"
2225install:
2326 - cabal --version
2427 - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
CONTRIBUTORS.md+1
@@ -69,6 +69,7 @@ This file lists the contributors to the PureScript compiler project, and the ter
6969- [@vkorablin](https://github.com/vkorablin) (Vladimir Korablin) - My existing contributions and all future contributions until further notice are Copyright Vladimir Korablin, and are licensed to the owners and users of the PureScript compiler project under the terms of the MIT license.
7070- [@zudov](https://github.com/zudov) (Konstantin Zudov) My existing contributions and all future contributions until further notice are Copyright Konstantin Zudov, and are licensed to the owners and users of the PureScript compiler project under the terms of the [MIT license](http://opensource.org/licenses/MIT).
7171- [@LiamGoodacre](https://github.com/LiamGoodacre) (Liam Goodacre) My existing contributions and all future contributions until further notice are Copyright Liam Goodacre, and are licensed to the owners and users of the PureScript compiler project under the terms of the [MIT license](http://opensource.org/licenses/MIT).
72+- [@bsermons](https://github.com/bsermons) (Brian Sermons) My existing contributions and all future contributions until further notice are Copyright Brian Sermons, and are licensed to the owners and users of the PureScript compiler project under the terms of the [MIT license](http://opensource.org/licenses/MIT).
7273
7374### Companies
7475
psc-ide-server/Main.hs+16−1
@@ -1,9 +1,24 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Main
4+-- Description : The server accepting commands for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- The server accepting commands for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE MultiParamTypeClasses #-}
418{-# LANGUAGE OverloadedStrings #-}
519{-# LANGUAGE PackageImports #-}
620{-# LANGUAGE TemplateHaskell #-}
21+
722module Main where
823
924import Prelude ()
@@ -21,7 +36,7 @@ import qualified Data.Text as T
2136import qualified Data.Text.IO as T
2237import Data.Version (showVersion)
2338import Language.PureScript.Ide
24−import Language.PureScript.Ide.CodecJSON
39+import Language.PureScript.Ide.Util
2540import Language.PureScript.Ide.Error
2641import Language.PureScript.Ide.Types
2742import Language.PureScript.Ide.Watcher
psc-ide-server/PROTOCOL.md+95−3
@@ -8,8 +8,9 @@ this document.
88
99## Command:
1010### Load
11−The `load` command "loads" the requested modules into the server
12−for completion and type info.
11+The `load` command "loads" the requested modules into the server for completion
12+and type info. If the `params` object is left off, the `load` command will try
13+to detect all the compiled modules in your project and load them.
1314
1415**Params:**
1516 - `modules :: (optional) [ModuleName]`: A list of modules to load.
@@ -21,7 +22,7 @@ for completion and type info.
2122```json
2223{
2324 "command": "load",
24− "params": {
25+ "params": (optional) {
2526 "modules": (optional)["Module.Name1", "Module.Name2"],
2627 "dependencies": (optional)["Module.Name3"]
2728 }
@@ -148,6 +149,97 @@ The following format is returned as the Result:
148149```
149150You should then be able to replace the affected line of code in the editor with the new suggestions.
150151
152+### Import
153+
154+For now all of the import related commands work with a file on the filesystem.
155+
156+You can specify it with the `file` parameter.
157+
158+If you supply the optional `outfile` parameter, the output will be written to
159+that file, and an info message will be returned from the client.
160+
161+If you don't supply `outfile`, the server responds with a list of strings which,
162+when inserted into a file linewise create the module with the applied changes.
163+
164+Arguments:
165+
166+- `file` :: String
167+- `outfile` :: Maybe String
168+- `filters` :: Maybe [Filter]
169+
170+Example:
171+
172+```json
173+{
174+ "command": "import",
175+ "params": {
176+ "file": "/home/creek/Documents/chromacannon/src/Main.purs",
177+ "outfile": "/home/creek/Documents/chromacannon/src/Main.purs",
178+ "filters": [{
179+ "filter": "modules",
180+ "params": {
181+ "modules": ["My.Module"]
182+ }
183+ }],
184+ "importCommand": {
185+ "yadda": "yadda"
186+ }
187+ }
188+}
189+```
190+
191+
192+#### Subcommand `addImplicitImport`
193+
194+This command just adds an unqualified import for the given modulename.
195+
196+Arguments:
197+- `moduleName :: String`
198+
199+Example:
200+```json
201+{
202+ "command": "import",
203+ "params": {
204+ "file": "/home/creek/Documents/chromacannon/src/Main.purs",
205+ "importCommand": {
206+ "importCommand": "addImplicitImport",
207+ "module": "Data.Array.LOL"
208+ }
209+ }
210+}
211+```
212+#### Subcommand `addImport`
213+
214+This command takes an identifier and searches the currently loaded modules for
215+it. If it finds no matches it responds with an Error. If it finds exactly one
216+match it adds the import and returns. If it finds more than one match it
217+responds with a list of the found matches as completions like the complete
218+command.
219+
220+You can also supply a list of filters like the ones for completion. This way you
221+can narrow down the search to a certain module and resolve the case in which
222+more then one match was found.
223+
224+Arguments:
225+- `moduleName :: String`
226+- `filters :: [Filter]`
227+
228+Example:
229+```json
230+{
231+ "command": "import",
232+ "params": {
233+ "file": "/home/creek/Documents/chromacannon/src/Demo.purs",
234+ "outfile": "/home/creek/Documents/chromacannon/src/Demo.purs",
235+ "importCommand": {
236+ "importCommand": "addImport",
237+ "identifier": "bind"
238+ }
239+ }
240+}
241+```
242+
151243### Pursuit
152244The `pursuit` command looks up the packages/completions for a given identifier from Pursuit.
153245
purescript.cabal+18−4
@@ -36,6 +36,7 @@ extra-source-files: examples/passing/*.purs
3636 , tests/support/flattened/*.purs
3737 , tests/support/flattened/*.js
3838 , tests/support/psci/*.purs
39+ , tests/support/pscide/src/*.purs
3940 , stack.yaml
4041 , stack-lts-5.yaml
4142 , stack-nightly.yaml
@@ -201,7 +202,6 @@ library
201202 Language.PureScript.Ide.Command
202203 Language.PureScript.Ide.Externs
203204 Language.PureScript.Ide.Error
204− Language.PureScript.Ide.CodecJSON
205205 Language.PureScript.Ide.Pursuit
206206 Language.PureScript.Ide.Completion
207207 Language.PureScript.Ide.Matcher
@@ -212,6 +212,8 @@ library
212212 Language.PureScript.Ide.SourceFile
213213 Language.PureScript.Ide.Watcher
214214 Language.PureScript.Ide.Reexports
215+ Language.PureScript.Ide.Imports
216+ Language.PureScript.Ide.Util
215217
216218 Control.Monad.Logger
217219 Control.Monad.Supply
@@ -313,7 +315,7 @@ executable psc-bundle
313315
314316executable psc-ide-server
315317 main-is: Main.hs
316− other-modules:
318+ other-modules: Paths_purescript
317319 other-extensions:
318320 build-depends: base >=4 && <5
319321 , purescript -any
@@ -333,7 +335,7 @@ executable psc-ide-server
333335
334336executable psc-ide-client
335337 main-is: Main.hs
336− other-modules:
338+ other-modules: Paths_purescript
337339 other-extensions:
338340 build-depends: base >=4 && <5
339341 , mtl -any
@@ -350,7 +352,8 @@ test-suite tests
350352 transformers -any, process -any, transformers-compat -any, time -any,
351353 Glob -any, aeson-better-errors -any, bytestring -any, aeson -any,
352354 base-compat -any, haskeline >= 0.7.0.0, optparse-applicative -any,
353− boxes -any, HUnit -any, hspec -any, hspec-discover -any, stm -any, text -any
355+ boxes -any, HUnit -any, hspec -any, hspec-discover -any, stm -any, text -any,
356+ vector -any, utf8-string -any
354357 ghc-options: -Wall
355358 type: exitcode-stdio-1.0
356359 main-is: Main.hs
@@ -361,5 +364,16 @@ test-suite tests
361364 TestPsci
362365 TestPscIde
363366 PscIdeSpec
367+ Language.PureScript.Ide.FilterSpec
368+ Language.PureScript.Ide.ImportsSpec
369+ Language.PureScript.Ide.Imports.IntegrationSpec
370+ Language.PureScript.Ide.Integration
371+ Language.PureScript.Ide.MatcherSpec
372+ Language.PureScript.Ide.ReexportsSpec
373+ Language.PureScript.IdeSpec
374+ PSCi.Completion
375+ PSCi.Directive
376+ PSCi.Module
377+ PSCi.Types
364378 buildable: True
365379 hs-source-dirs: tests psci
src/Language/PureScript/Ide.hs+63−21
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide
4+-- Description : Interface for the psc-ide-server
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Interface for the psc-ide-server
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE MultiParamTypeClasses #-}
@@ -6,7 +20,11 @@
620{-# LANGUAGE TemplateHaskell #-}
721{-# LANGUAGE TupleSections #-}
822
9−module Language.PureScript.Ide where
23+module Language.PureScript.Ide
24+ ( handleCommand
25+ -- for tests
26+ , printModules
27+ ) where
1028
1129import Prelude ()
1230import Prelude.Compat
@@ -27,12 +45,14 @@ import Language.PureScript.Ide.Completion
2745import Language.PureScript.Ide.Error
2846import Language.PureScript.Ide.Externs
2947import Language.PureScript.Ide.Filter
48+import Language.PureScript.Ide.Imports hiding (Import)
3049import Language.PureScript.Ide.Matcher
3150import Language.PureScript.Ide.Pursuit
3251import Language.PureScript.Ide.Reexports
3352import Language.PureScript.Ide.SourceFile
3453import Language.PureScript.Ide.State
3554import Language.PureScript.Ide.Types
55+import Language.PureScript.Ide.Util
3656import System.Directory
3757import System.FilePath
3858import System.Exit
@@ -40,39 +60,48 @@ import System.Exit
4060
4161handleCommand :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
4262 Command -> m Success
63+handleCommand (Load [] []) = loadAllModules
4364handleCommand (Load modules deps) =
44− loadModulesAndDeps modules deps
65+ loadModulesAndDeps modules deps
4566handleCommand (Type search filters) =
46− findType search filters
67+ findType search filters
4768handleCommand (Complete filters matcher) =
48− findCompletions filters matcher
69+ findCompletions filters matcher
4970handleCommand (Pursuit query Package) =
50− findPursuitPackages query
71+ findPursuitPackages query
5172handleCommand (Pursuit query Identifier) =
52− findPursuitCompletions query
73+ findPursuitCompletions query
5374handleCommand (List LoadedModules) =
54− printModules
75+ printModules
5576handleCommand (List AvailableModules) =
56− listAvailableModules
77+ listAvailableModules
5778handleCommand (List (Imports fp)) =
58− importsForFile fp
79+ importsForFile fp
5980handleCommand (CaseSplit l b e wca t) =
60− caseSplit l b e wca t
81+ caseSplit l b e wca t
6182handleCommand (AddClause l wca) =
62− pure $ addClause l wca
83+ pure $ addClause l wca
84+handleCommand (Import fp outfp _ (AddImplicitImport mn)) = do
85+ rs <- addImplicitImport fp mn
86+ answerRequest outfp rs
87+handleCommand (Import fp outfp filters (AddImportForIdentifier ident)) = do
88+ rs <- addImportForIdentifier fp ident filters
89+ case rs of
90+ Right rs' -> answerRequest outfp rs'
91+ Left question -> pure $ CompletionResult (mapMaybe completionFromMatch question)
6392handleCommand Cwd =
64− TextResult . T.pack <$> liftIO getCurrentDirectory
93+ TextResult . T.pack <$> liftIO getCurrentDirectory
6594handleCommand Quit = liftIO exitSuccess
6695
6796findCompletions :: (PscIde m, MonadLogger m) =>
6897 [Filter] -> Matcher -> m Success
6998findCompletions filters matcher =
70− CompletionResult . getCompletions filters matcher <$> getAllModulesWithReexports
99+ CompletionResult . mapMaybe completionFromMatch . getCompletions filters matcher <$> getAllModulesWithReexports
71100
72101findType :: (PscIde m, MonadLogger m) =>
73102 DeclIdent -> [Filter] -> m Success
74103findType search filters =
75− CompletionResult . getExactMatches search filters <$> getAllModulesWithReexports
104+ CompletionResult . mapMaybe completionFromMatch . getExactMatches search filters <$> getAllModulesWithReexports
76105
77106findPursuitCompletions :: (MonadIO m, MonadLogger m) =>
78107 PursuitQuery -> m Success
@@ -179,6 +208,26 @@ loadModule mn = do
179208 $(logDebug) ("Loaded extern file at: " <> T.pack path)
180209 pure ("Loaded extern file at: " <> T.pack path)
181210
211+loadAllModules :: (PscIde m, MonadLogger m, MonadError PscIdeError m) => m Success
212+loadAllModules = do
213+ outputPath <- confOutputPath . envConfiguration <$> ask
214+ cwd <- liftIO getCurrentDirectory
215+ let outputDirectory = cwd </> outputPath
216+ liftIO (getDirectoryContents outputDirectory)
217+ >>= liftIO . traverse (getExternsPath outputDirectory)
218+ >>= traverse_ loadExtern . catMaybes
219+ pure (TextResult "All modules loaded.")
220+ where
221+ getExternsPath :: FilePath -> FilePath -> IO (Maybe FilePath)
222+ getExternsPath outputDirectory d
223+ | d `elem` [".", ".."] = pure Nothing
224+ | otherwise = do
225+ let file = outputDirectory </> d </> "externs.json"
226+ ex <- doesFileExist file
227+ if ex
228+ then pure (Just file)
229+ else pure Nothing
230+
182231filePathFromModule :: (PscIde m, MonadError PscIdeError m) =>
183232 ModuleIdent -> m FilePath
184233filePathFromModule moduleName = do
@@ -190,10 +239,3 @@ filePathFromModule moduleName = do
190239 then pure path
191240 else throwError (ModuleFileNotFound moduleName)
192241
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.hs+47−38
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.CaseSplit
4+-- Description : Casesplitting and adding function clauses
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Casesplitting and adding function clauses
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE DataKinds #-}
317{-# LANGUAGE FlexibleContexts #-}
@@ -26,23 +40,18 @@ import Data.List (find)
2640import Data.Monoid
2741import Data.Text (Text)
2842import qualified Data.Text as T
29−import Language.PureScript.AST
30−import Language.PureScript.Environment
43+import qualified Language.PureScript as P
44+
3145import Language.PureScript.Externs
3246import Language.PureScript.Ide.Error
3347import Language.PureScript.Ide.Externs (unwrapPositioned)
3448import Language.PureScript.Ide.State
35−import Language.PureScript.Ide.Types hiding (Type)
36−import Language.PureScript.Names
37−import Language.PureScript.Parser.Common (runTokenParser)
38−import Language.PureScript.Parser.Declarations
39−import Language.PureScript.Parser.Lexer (lex)
40−import Language.PureScript.Parser.Types
41−import Language.PureScript.Pretty
42−import Language.PureScript.Types
43−import Text.Parsec as P
44−
45−type Constructor = (ProperName 'ConstructorName, [Type])
49+import Language.PureScript.Ide.Types
50+import Language.PureScript.Ide.Util
51+
52+import Text.Parsec as Parsec
53+
54+type Constructor = (P.ProperName 'P.ConstructorName, [P.Type])
4655
4756newtype WildcardAnnotations = WildcardAnnotations Bool
4857
@@ -57,13 +66,13 @@ caseSplit :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
5766caseSplit q = do
5867 type' <- parseType' (T.unpack q)
5968 (tc, args) <- splitTypeConstructor type'
60− (EDType _ _ (DataType typeVars ctors)) <- findTypeDeclaration tc
61− let applyTypeVars = everywhereOnTypes (replaceAllTypeVars (zip (map fst typeVars) args))
69+ (EDType _ _ (P.DataType typeVars ctors)) <- findTypeDeclaration tc
70+ let applyTypeVars = P.everywhereOnTypes (P.replaceAllTypeVars (zip (map fst typeVars) args))
6271 let appliedCtors = map (second (map applyTypeVars)) ctors
6372 pure appliedCtors
6473
6574findTypeDeclaration :: (PscIde m, MonadLogger m, MonadError PscIdeError m) =>
66− ProperName 'TypeName -> m ExternsDeclaration
75+ P.ProperName 'P.TypeName -> m ExternsDeclaration
6776findTypeDeclaration q = do
6877 efs <- getExternFiles
6978 let m = getFirst $ foldMap (findTypeDeclaration' q) efs
@@ -72,7 +81,7 @@ findTypeDeclaration q = do
7281 Nothing -> throwError (GeneralError "Not Found")
7382
7483findTypeDeclaration' ::
75− ProperName 'TypeName
84+ P.ProperName 'P.TypeName
7685 -> ExternsFile
7786 -> First ExternsDeclaration
7887findTypeDeclaration' t ExternsFile{..} =
@@ -81,25 +90,25 @@ findTypeDeclaration' t ExternsFile{..} =
8190 _ -> False) efDeclarations
8291
8392splitTypeConstructor :: (MonadError PscIdeError m) =>
84− Type -> m (ProperName 'TypeName, [Type])
93+ P.Type -> m (P.ProperName 'P.TypeName, [P.Type])
8594splitTypeConstructor = go []
8695 where
87− go acc (TypeApp ty arg) = go (arg : acc) ty
88− go acc (TypeConstructor tc) = pure (disqualify tc, acc)
96+ go acc (P.TypeApp ty arg) = go (arg : acc) ty
97+ go acc (P.TypeConstructor tc) = pure (P.disqualify tc, acc)
8998 go _ _ = throwError (GeneralError "Failed to read TypeConstructor")
9099
91100prettyCtor :: WildcardAnnotations -> Constructor -> Text
92−prettyCtor _ (ctorName, []) = T.pack (runProperName ctorName)
101+prettyCtor _ (ctorName, []) = runProperNameT ctorName
93102prettyCtor wsa (ctorName, ctorArgs) =
94− "("<> T.pack (runProperName ctorName) <> " "
103+ "("<> runProperNameT ctorName <> " "
95104 <> T.unwords (map (prettyPrintWildcard wsa) ctorArgs) <>")"
96105
97−prettyPrintWildcard :: WildcardAnnotations -> Type -> Text
106+prettyPrintWildcard :: WildcardAnnotations -> P.Type -> Text
98107prettyPrintWildcard (WildcardAnnotations True) = prettyWildcard
99108prettyPrintWildcard (WildcardAnnotations False) = const "_"
100109
101−prettyWildcard :: Type -> Text
102−prettyWildcard t = "( _ :: " <> T.strip (T.pack (prettyPrintTypeAtom t)) <> ")"
110+prettyWildcard :: P.Type -> Text
111+prettyWildcard t = "( _ :: " <> T.strip (T.pack (P.prettyPrintTypeAtom t)) <> ")"
103112
104113-- | Constructs Patterns to insert into a sourcefile
105114makePattern :: Text -- ^ Current line
@@ -116,38 +125,38 @@ addClause :: Text -> WildcardAnnotations -> [Text]
116125addClause s wca =
117126 let (fName, fType) = parseTypeDeclaration' (T.unpack s)
118127 (args, _) = splitFunctionType fType
119− template = T.pack (runIdent fName) <> " " <>
128+ template = runIdentT fName <> " " <>
120129 T.unwords (map (prettyPrintWildcard wca) args) <>
121− " = ?" <> (T.strip . T.pack . runIdent $ fName)
130+ " = ?" <> (T.strip . runIdentT $ fName)
122131 in [s, template]
123132
124133parseType' :: (MonadError PscIdeError m) =>
125− String -> m Type
134+ String -> m P.Type
126135parseType' s =
127− case lex "<psc-ide>" s >>= runTokenParser "<psc-ide>" (parseType <* P.eof) of
136+ case P.lex "<psc-ide>" s >>= P.runTokenParser "<psc-ide>" (P.parseType <* Parsec.eof) of
128137 Right type' -> pure type'
129138 Left err ->
130139 throwError (GeneralError ("Parsing the splittype failed with:"
131140 ++ show err))
132141
133−parseTypeDeclaration' :: String -> (Ident, Type)
142+parseTypeDeclaration' :: String -> (P.Ident, P.Type)
134143parseTypeDeclaration' s =
135144 let x = do
136− ts <- lex "" s
137− runTokenParser "" (parseDeclaration <* P.eof) ts
145+ ts <- P.lex "" s
146+ P.runTokenParser "" (P.parseDeclaration <* Parsec.eof) ts
138147 in
139148 case unwrapPositioned <$> x of
140− Right (TypeDeclaration i t) -> (i, t)
149+ Right (P.TypeDeclaration i t) -> (i, t)
141150 y -> error (show y)
142151
143−splitFunctionType :: Type -> ([Type], Type)
152+splitFunctionType :: P.Type -> ([P.Type], P.Type)
144153splitFunctionType t = (arguments, returns)
145154 where
146155 returns = last splitted
147156 arguments = init splitted
148157 splitted = splitType' t
149− splitType' (ForAll _ t' _) = splitType' t'
150− splitType' (ConstrainedType _ t') = splitType' t'
151− splitType' (TypeApp (TypeApp t' lhs) rhs)
152− | t' == tyFunction = lhs : splitType' rhs
158+ splitType' (P.ForAll _ t' _) = splitType' t'
159+ splitType' (P.ConstrainedType _ t') = splitType' t'
160+ splitType' (P.TypeApp (P.TypeApp t' lhs) rhs)
161+ | t' == P.tyFunction = lhs : splitType' rhs
153162 splitType' t' = [t']
src/Language/PureScript/Ide/CodecJSON.hsdeleted−13
@@ -1,13 +0,0 @@
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.hs+72−20
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Command
4+-- Description : Datatypes for the commands psc-ide accepts
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Datatypes for the commands psc-ide accepts
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE MultiParamTypeClasses #-}
216{-# LANGUAGE OverloadedStrings #-}
317{-# LANGUAGE ScopedTypeVariables #-}
@@ -11,33 +25,64 @@ import Control.Monad
1125import Data.Aeson
1226import Data.Maybe
1327import Data.Text (Text)
28+import Language.PureScript (ModuleName,
29+ moduleNameFromString)
1430import Language.PureScript.Ide.CaseSplit
1531import Language.PureScript.Ide.Filter
1632import Language.PureScript.Ide.Matcher
1733import Language.PureScript.Ide.Types
1834
1935data 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
36+ = Load
37+ { loadModules :: [ModuleIdent]
38+ , loadDependencies :: [ModuleIdent]
39+ }
40+ | Type
41+ { typeSearch :: DeclIdent
42+ , typeFilters :: [Filter]
43+ }
44+ | Complete
45+ { completeFilters :: [Filter]
46+ , completeMatcher :: Matcher
47+ }
48+ | Pursuit
49+ { pursuitQuery :: PursuitQuery
50+ , pursuitSearchType :: PursuitSearchType
51+ }
52+ | CaseSplit
53+ { caseSplitLine :: Text
3154 , caseSplitBegin :: Int
3255 , caseSplitEnd :: Int
3356 , caseSplitAnnotations :: WildcardAnnotations
34− , caseSplitType :: Type}
35− | AddClause {
36− addClauseLine :: Text
37− , addClauseAnnotations :: WildcardAnnotations}
57+ , caseSplitType :: Text
58+ }
59+ | AddClause
60+ { addClauseLine :: Text
61+ , addClauseAnnotations :: WildcardAnnotations
62+ }
63+ -- Import InputFile OutputFile
64+ | Import FilePath (Maybe FilePath) [Filter] ImportCommand
65+ | List { listType :: ListType }
3866 | Cwd
3967 | Quit
4068
69+data ImportCommand
70+ = AddImplicitImport ModuleName
71+ | AddImportForIdentifier DeclIdent
72+ deriving (Show, Eq)
73+
74+instance FromJSON ImportCommand where
75+ parseJSON = withObject "ImportCommand" $ \o -> do
76+ (command :: String) <- o .: "importCommand"
77+ case command of
78+ "addImplicitImport" -> do
79+ mn <- o .: "module"
80+ pure (AddImplicitImport (moduleNameFromString mn))
81+ "addImport" -> do
82+ ident <- o .: "identifier"
83+ pure (AddImportForIdentifier ident)
84+ _ -> mzero
85+
4186data ListType = LoadedModules | Imports FilePath | AvailableModules
4287
4388instance FromJSON ListType where
@@ -60,11 +105,11 @@ instance FromJSON Command where
60105 return $ List (fromMaybe LoadedModules listType')
61106 "cwd" -> return Cwd
62107 "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)
108+ "load" ->
109+ maybe (pure (Load [] [])) (\params -> do
110+ mods <- params .:? "modules"
111+ deps <- params .:? "dependencies"
112+ pure $ Load (fromMaybe [] mods) (fromMaybe [] deps)) =<< o .:? "params"
68113 "type" -> do
69114 params <- o .: "params"
70115 search <- params .: "search"
@@ -97,5 +142,12 @@ instance FromJSON Command where
97142 return $ AddClause line (if annotations
98143 then explicitAnnotations
99144 else noAnnotations)
145+ "import" -> do
146+ params <- o .: "params"
147+ fp <- params .: "file"
148+ out <- params .:? "outfile"
149+ filters <- params .:? "filters"
150+ importCommand <- params .: "importCommand"
151+ pure $ Import fp out (fromMaybe [] filters) importCommand
100152 _ -> mzero
101153
src/Language/PureScript/Ide/Completion.hs+7−10
@@ -13,23 +13,20 @@ import Language.PureScript.Ide.Types
1313
1414-- | Applies the CompletionFilters and the Matcher to the given Modules
1515-- and sorts the found Completions according to the Matching Score
16−getCompletions :: [Filter] -> Matcher -> [Module] -> [Completion]
16+getCompletions :: [Filter] -> Matcher -> [Module] -> [Match]
1717getCompletions filters matcher modules =
1818 runMatcher matcher $ completionsFromModules (applyFilters filters modules)
1919
20−getExactMatches :: DeclIdent -> [Filter] -> [Module] -> [Completion]
20+getExactMatches :: DeclIdent -> [Filter] -> [Module] -> [Match]
2121getExactMatches search filters modules =
2222 completionsFromModules $
2323 applyFilters (equalityFilter search : filters) modules
2424
25−completionsFromModules :: [Module] -> [Completion]
25+completionsFromModules :: [Module] -> [Match]
2626completionsFromModules = foldMap completionFromModule
2727 where
28− completionFromModule :: Module -> [Completion]
29− completionFromModule (moduleIdent, decls) = mapMaybe (completionFromDecl moduleIdent) decls
28+ completionFromModule :: Module -> [Match]
29+ completionFromModule (moduleIdent, decls) = mapMaybe (matchFromDecl moduleIdent) decls
3030
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
31+matchFromDecl :: ModuleIdent -> ExternDecl -> Maybe Match
32+matchFromDecl mi = Just . Match mi
src/Language/PureScript/Ide/Error.hs+15−6
@@ -1,6 +1,20 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Error
4+-- Description : Error types for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Error types for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE OverloadedStrings #-}
216module Language.PureScript.Ide.Error
3− (ErrorMsg, PscIdeError(..), textError, first)
17+ (ErrorMsg, PscIdeError(..), textError)
418 where
519
620import Data.Aeson
@@ -35,8 +49,3 @@ textError (ParseError parseError msg) = pack $ msg <> ": " <> show (escape parse
3549 -- escape newlines and other special chars so we can send the error over the socket as a single line
3650 escape :: P.ParseError -> String
3751 escape = show
38−
39−-- | Specialized version of `first` from `Data.Bifunctors`
40−first :: (a -> b) -> Either a r -> Either b r
41−first f (Left x) = Left (f x)
42−first _ (Right r2) = Right r2
src/Language/PureScript/Ide/Externs.hs+58−46
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Externs
4+-- Description : Handles externs files for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Handles externs files for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE FlexibleContexts #-}
216{-# LANGUAGE MultiParamTypeClasses #-}
317{-# LANGUAGE OverloadedStrings #-}
@@ -5,34 +19,32 @@
519{-# LANGUAGE ScopedTypeVariables #-}
620
721module Language.PureScript.Ide.Externs
8− (
9− ExternDecl(..),
22+ ( ExternDecl(..),
1023 ModuleIdent,
1124 DeclIdent,
12− Type,
13− Fixity(..),
1425 readExternFile,
1526 convertExterns,
1627 unwrapPositioned,
1728 unwrapPositionedRef
1829 ) where
1930
20−import Prelude ()
31+import Prelude ()
2132import Prelude.Compat
2233
2334import Control.Monad.Error.Class
2435import 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 (..))
36+import Data.List (nub)
37+import Data.Maybe (mapMaybe)
38+import Data.Monoid
39+import Data.Text (Text)
40+import qualified Data.Text as T
41+import qualified Data.Text.IO as T
42+import Language.PureScript.Ide.Error (PscIdeError (..))
3343import Language.PureScript.Ide.Types
34−import qualified Language.PureScript.Names as N
35−import qualified Language.PureScript.Pretty as PP
44+import Language.PureScript.Ide.Util
45+
46+import qualified Language.PureScript as P
47+import qualified Language.PureScript.Externs as PE
3648
3749readExternFile :: (MonadIO m, MonadError PscIdeError m) =>
3850 FilePath -> m PE.ExternsFile
@@ -42,17 +54,14 @@ readExternFile fp = do
4254 Nothing -> throwError . GeneralError $ "Parsing the extern at: " ++ fp ++ " failed"
4355 Just externs -> pure externs
4456
45−moduleNameToText :: N.ModuleName -> Text
46−moduleNameToText = T.pack . N.runModuleName
57+moduleNameToText :: P.ModuleName -> Text
58+moduleNameToText = T.pack . P.runModuleName
4759
48−properNameToText :: N.ProperName a -> Text
49−properNameToText = T.pack . N.runProperName
50−
51−identToText :: N.Ident -> Text
52−identToText = T.pack . N.runIdent
60+identToText :: P.Ident -> Text
61+identToText = T.pack . P.runIdent
5362
5463convertExterns :: PE.ExternsFile -> Module
55−convertExterns ef = (moduleName, exportDecls ++ importDecls ++ otherDecls)
64+convertExterns ef = (moduleName, exportDecls ++ importDecls ++ decls)
5665 where
5766 moduleName = moduleNameToText (PE.efModuleName ef)
5867 importDecls = convertImport <$> PE.efImports ef
@@ -61,42 +70,45 @@ convertExterns ef = (moduleName, exportDecls ++ importDecls ++ otherDecls)
6170 -- operatorDecls = convertOperator <$> PE.efFixities ef
6271 otherDecls = mapMaybe convertDecl (PE.efDeclarations ef)
6372
73+ typeClassFilter = foldMap removeTypeDeclarationsForClass (filter isTypeClassDeclaration otherDecls)
74+ decls = nub $ appEndo typeClassFilter otherDecls
75+
76+removeTypeDeclarationsForClass :: ExternDecl -> Endo [ExternDecl]
77+removeTypeDeclarationsForClass (TypeClassDeclaration n) = Endo (filter notDuplicate)
78+ where notDuplicate (TypeDeclaration n' _) = runProperNameT n /= runProperNameT n'
79+ notDuplicate (TypeSynonymDeclaration n' _) = runProperNameT n /= runProperNameT n'
80+ notDuplicate _ = True
81+removeTypeDeclarationsForClass _ = mempty
82+
83+isTypeClassDeclaration :: ExternDecl -> Bool
84+isTypeClassDeclaration TypeClassDeclaration{} = True
85+isTypeClassDeclaration _ = False
86+
6487convertImport :: PE.ExternsImport -> ExternDecl
6588convertImport ei = Dependency
6689 (moduleNameToText (PE.eiModule ei))
6790 []
6891 (moduleNameToText <$> PE.eiImportedAs ei)
6992
70−convertExport :: D.DeclarationRef -> Maybe ExternDecl
71−convertExport (D.ModuleRef mn) = Just (Export (moduleNameToText mn))
93+convertExport :: P.DeclarationRef -> Maybe ExternDecl
94+convertExport (P.ModuleRef mn) = Just (Export (moduleNameToText mn))
7295convertExport _ = Nothing
7396
7497convertDecl :: PE.ExternsDeclaration -> Maybe ExternDecl
75−convertDecl PE.EDType{..} = Just $
76− DataDecl
77− (properNameToText edTypeName)
78− (packAndStrip (PP.prettyPrintKind edTypeKind))
98+convertDecl PE.EDType{..} = Just $ TypeDeclaration edTypeName edTypeKind
7999convertDecl PE.EDTypeSynonym{..} = Just $
80− DataDecl
81− (properNameToText edTypeSynonymName)
82− (packAndStrip (PP.prettyPrintType edTypeSynonymType))
100+ TypeSynonymDeclaration edTypeSynonymName edTypeSynonymType
83101convertDecl PE.EDDataConstructor{..} = Just $
84− DataDecl
85− (properNameToText edDataCtorName)
86− (packAndStrip (PP.prettyPrintType edDataCtorType))
102+ DataConstructor (runProperNameT edDataCtorName) edDataCtorTypeCtor edDataCtorType
87103convertDecl 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
104+ ValueDeclaration (identToText edValueName) edValueType
105+convertDecl PE.EDClass{..} = Just $ TypeClassDeclaration edClassName
106+convertDecl PE.EDInstance{} = Nothing
95107
96−unwrapPositioned :: D.Declaration -> D.Declaration
97−unwrapPositioned (D.PositionedDeclaration _ _ x) = x
108+unwrapPositioned :: P.Declaration -> P.Declaration
109+unwrapPositioned (P.PositionedDeclaration _ _ x) = x
98110unwrapPositioned x = x
99111
100−unwrapPositionedRef :: D.DeclarationRef -> D.DeclarationRef
101−unwrapPositionedRef (D.PositionedDeclarationRef _ _ x) = x
112+unwrapPositionedRef :: P.DeclarationRef -> P.DeclarationRef
113+unwrapPositionedRef (P.PositionedDeclarationRef _ _ x) = x
102114unwrapPositionedRef x = x
src/Language/PureScript/Ide/Filter.hs+28−11
@@ -1,10 +1,30 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Filter
4+-- Description : Filters for psc-ide commands
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Filters for psc-ide commands
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE GeneralizedNewtypeDeriving #-}
216{-# LANGUAGE OverloadedStrings #-}
317{-# LANGUAGE ScopedTypeVariables #-}
18+
419module Language.PureScript.Ide.Filter
5− (Filter, moduleFilter, prefixFilter, equalityFilter, dependencyFilter,
6− runFilter, applyFilters)
7− where
20+ ( Filter
21+ , moduleFilter
22+ , prefixFilter
23+ , equalityFilter
24+ , dependencyFilter
25+ , runFilter
26+ , applyFilters
27+ ) where
828
929import Prelude ()
1030import Prelude.Compat
@@ -16,6 +36,7 @@ import Data.Maybe (listToMaybe, mapMaybe)
1636import Data.Monoid
1737import Data.Text (Text, isPrefixOf)
1838import Language.PureScript.Ide.Types
39+import Language.PureScript.Ide.Util
1940
2041newtype Filter = Filter (Endo [Module]) deriving(Monoid)
2142
@@ -57,10 +78,9 @@ prefixFilter "" = mkFilter id
5778prefixFilter t = mkFilter $ identFilter prefix t
5879 where
5980 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
81+ prefix Export{} _ = False
82+ prefix Dependency{} _ = False
83+ prefix ed search = search `isPrefixOf` identifierFromExternDecl ed
6484
6585
6686-- | Only keeps Identifiers that are equal to the search string
@@ -68,10 +88,7 @@ equalityFilter :: Text -> Filter
6888equalityFilter = mkFilter . identFilter equality
6989 where
7090 equality :: ExternDecl -> Text -> Bool
71− equality (FunctionDecl name _) prefix = prefix == name
72− equality (DataDecl name _) prefix = prefix == name
73− equality _ _ = False
74−
91+ equality ed search = identifierFromExternDecl ed == search
7592
7693identFilter :: (ExternDecl -> Text -> Bool ) -> Text -> [Module] -> [Module]
7794identFilter predicate search =
src/Language/PureScript/Ide/Imports.hsnew file+355
@@ -0,0 +1,355 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Imports
4+-- Description : Provides functionality to manage imports
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Provides functionality to manage imports
13+-----------------------------------------------------------------------------
14+
15+{-# LANGUAGE ConstraintKinds #-}
16+{-# LANGUAGE FlexibleContexts #-}
17+{-# LANGUAGE LambdaCase #-}
18+{-# LANGUAGE MultiParamTypeClasses #-}
19+{-# LANGUAGE OverloadedStrings #-}
20+{-# LANGUAGE PackageImports #-}
21+
22+module Language.PureScript.Ide.Imports
23+ ( addImplicitImport
24+ , addImportForIdentifier
25+ , answerRequest
26+ -- for tests
27+ , parseImport
28+ , prettyPrintImportSection
29+ , addImplicitImport'
30+ , addExplicitImport'
31+ , sliceImportSection
32+ , prettyPrintImport'
33+ , Import(Import)
34+ )
35+ where
36+
37+import Control.Applicative ((<|>))
38+import Control.Monad.Error.Class
39+import Control.Monad.IO.Class
40+import "monad-logger" Control.Monad.Logger
41+import Data.Bifunctor (first, second)
42+import Data.Function (on)
43+import qualified Data.List as List
44+import Data.Maybe (isNothing)
45+import Data.Monoid ((<>))
46+import Data.Text (Text)
47+import qualified Data.Text as T
48+import qualified Data.Text.IO as TIO
49+import qualified Language.PureScript as P
50+import Language.PureScript.Ide.Completion
51+import Language.PureScript.Ide.Error
52+import Language.PureScript.Ide.Externs (unwrapPositioned,
53+ unwrapPositionedRef)
54+import Language.PureScript.Ide.Filter
55+import Language.PureScript.Ide.State
56+import Language.PureScript.Ide.Types
57+import Language.PureScript.Ide.Util
58+
59+data Import = Import P.ModuleName P.ImportDeclarationType (Maybe P.ModuleName)
60+ deriving (Eq, Show)
61+
62+instance Ord Import where
63+ compare = compImport
64+
65+compImportType :: P.ImportDeclarationType -> P.ImportDeclarationType -> Ordering
66+compImportType P.Implicit P.Implicit = EQ
67+compImportType P.Implicit _ = LT
68+compImportType (P.Explicit _) (P.Hiding _) = LT
69+compImportType (P.Explicit _) (P.Explicit _) = EQ
70+compImportType (P.Explicit _) P.Implicit = GT
71+compImportType (P.Hiding _) (P.Hiding _) = EQ
72+compImportType (P.Hiding _) _ = GT
73+
74+compImport :: Import -> Import -> Ordering
75+compImport (Import n i q) (Import n' i' q')
76+ | compImportType i i' /= EQ = compImportType i i'
77+ -- This means that for a stable sort, the first implicit import will stay
78+ -- the first implicit import
79+ | P.isImplicit i && isNothing q = LT
80+ | P.isImplicit i && isNothing q' = GT
81+ | otherwise = compare n n'
82+
83+-- | Reads a file and returns the (lines before the imports, the imports, the
84+-- lines after the imports)
85+parseImportsFromFile :: (MonadIO m, MonadError PscIdeError m) =>
86+ FilePath -> m (P.ModuleName, [Text], [Import], [Text])
87+parseImportsFromFile fp = do
88+ file <- liftIO (TIO.readFile fp)
89+ case sliceImportSection (T.lines file) of
90+ Right res -> pure res
91+ Left err -> throwError (GeneralError err)
92+
93+parseImportsWithModuleName :: [Text] -> Either String (P.ModuleName, [Import])
94+parseImportsWithModuleName ls = do
95+ (P.Module _ _ mn decls _) <- moduleParse ls
96+ pure (mn, concatMap mkImport (unwrapPositioned <$> decls))
97+ where
98+ mkImport (P.ImportDeclaration mn (P.Explicit refs) qual _) =
99+ [Import mn (P.Explicit (unwrapPositionedRef <$> refs)) qual]
100+ mkImport (P.ImportDeclaration mn it qual _) = [Import mn it qual]
101+ mkImport _ = []
102+
103+sliceImportSection :: [Text] -> Either String (P.ModuleName, [Text], [Import], [Text])
104+sliceImportSection ts =
105+ case foldl step (ModuleHeader 0) (zip [0..] ts) of
106+ Res start end ->
107+ let
108+ (moduleHeader, (importSection, remainingFile)) =
109+ List.splitAt (succ (end - start)) `second` List.splitAt start ts
110+ in
111+ (\(mn, is) -> (mn, moduleHeader, is, remainingFile)) <$>
112+ parseImportsWithModuleName (moduleHeader <> importSection)
113+
114+ -- If we don't find any imports, we insert a newline after the module
115+ -- declaration and begin a new importsection
116+ ModuleHeader ix ->
117+ let (moduleHeader, remainingFile) = List.splitAt (succ ix) ts
118+ in
119+ (\(mn, is) -> (mn, moduleHeader ++ [""], is, remainingFile)) <$>
120+ parseImportsWithModuleName moduleHeader
121+ _ -> Left "Failed to detect the import section"
122+
123+data ImportStateMachine = ModuleHeader Int | ImportSection Int Int | Res Int Int
124+
125+-- | We start in the
126+--
127+-- * ModuleHeader state.
128+--
129+-- We skip every line we encounter, that doesn't start with "import". If we find
130+-- a line that starts with module we store that linenumber. Once we find a line
131+-- with "import" we store its linenumber as the start of the import section and
132+-- change into the
133+--
134+-- * ImportSection state
135+--
136+-- For any line that starts with import or whitespace(is thus indented) we
137+-- expand the end of the import section to that line and continue. If we
138+-- encounter a commented or empty line, we continue moving forward in the
139+-- ImportSection state but don't expand the import section end yet. This allows
140+-- us to exclude newlines or comments that directly follow the import section.
141+-- Once we encounter a line that is not a comment, newline, indentation or
142+-- import we switch into the
143+--
144+-- * Res state
145+--
146+-- , which just shortcuts to the end of the file and carries the detected import
147+-- section boundaries
148+step :: ImportStateMachine -> (Int, Text) -> ImportStateMachine
149+step (ModuleHeader mi) (ix, l)
150+ | T.isPrefixOf "module " l = ModuleHeader ix
151+ | T.isPrefixOf "import " l = ImportSection ix ix
152+ | otherwise = ModuleHeader mi
153+step (ImportSection start lastImportLine) (ix, l)
154+ | any (`T.isPrefixOf` l) ["import", " "] = ImportSection start ix
155+ | T.isPrefixOf "--" l || l == "" = ImportSection start lastImportLine
156+ | otherwise = Res start lastImportLine
157+step (Res start end) _ = Res start end
158+
159+moduleParse :: [Text] -> Either String P.Module
160+moduleParse t = first show $ do
161+ tokens <- (P.lex "" . T.unpack . T.unlines) t
162+ P.runTokenParser "<psc-ide>" P.parseModule tokens
163+
164+-- | Adds an implicit import like @import Prelude@ to a Sourcefile.
165+addImplicitImport :: (MonadIO m, MonadError PscIdeError m)
166+ => FilePath -- ^ The Sourcefile read from
167+ -> P.ModuleName -- ^ The module to import
168+ -> m [Text]
169+addImplicitImport fp mn = do
170+ (_, pre, imports, post) <- parseImportsFromFile fp
171+ let newImportSection = addImplicitImport' imports mn
172+ pure $ pre ++ newImportSection ++ post
173+
174+addImplicitImport' :: [Import] -> P.ModuleName -> [Text]
175+addImplicitImport' imports mn =
176+ -- We need to append the new import, because there could already be implicit
177+ -- imports and we need to preserve the order on these, as the first implicit
178+ -- import is the one that doesn't generate warnings.
179+ prettyPrintImportSection ( imports ++ [Import mn P.Implicit Nothing])
180+
181+-- | Adds an explicit import like @import Prelude (unit)@ to a Sourcefile. If an
182+-- explicit import already exists for the given module, it adds the identifier
183+-- to that imports list.
184+--
185+-- So @addExplicitImport "/File.purs" "bind" "Prelude"@ with an already existing
186+-- @import Prelude (bind)@ in the file File.purs returns @["import Prelude
187+-- (bind, unit)"]@
188+addExplicitImport :: (MonadIO m, MonadError PscIdeError m, MonadLogger m) =>
189+ FilePath -> ExternDecl -> P.ModuleName -> m [Text]
190+addExplicitImport fp decl moduleName = do
191+ (mn, pre, imports, post) <- parseImportsFromFile fp
192+ let newImportSection =
193+ -- TODO: Open an issue when this PR is merged, we should optimise this
194+ -- so that this case does not write to disc
195+ if mn == moduleName
196+ then imports
197+ else addExplicitImport' decl moduleName imports
198+ pure (pre ++ prettyPrintImportSection newImportSection ++ post)
199+
200+addExplicitImport' :: ExternDecl -> P.ModuleName -> [Import] -> [Import]
201+addExplicitImport' decl moduleName imports =
202+ let
203+ isImplicitlyImported =
204+ not . null $ filter (\case
205+ (Import mn P.Implicit Nothing) -> mn == moduleName
206+ _ -> False) imports
207+ matches (Import mn (P.Explicit _) Nothing) = mn == moduleName
208+ matches _ = False
209+ freshImport = Import moduleName (P.Explicit [refFromDeclaration decl]) Nothing
210+ in
211+ if isImplicitlyImported
212+ then imports
213+ else updateAtFirstOrPrepend matches (insertDeclIntoImport decl) freshImport imports
214+ where
215+ refFromDeclaration (TypeClassDeclaration n) = P.TypeClassRef n
216+ refFromDeclaration (DataConstructor n tn _) =
217+ P.TypeRef tn (Just [P.ProperName (T.unpack n)])
218+ refFromDeclaration (TypeDeclaration n _) = P.TypeRef n (Just [])
219+ refFromDeclaration d =
220+ let
221+ ident = T.unpack (identifierFromExternDecl d)
222+ in
223+ P.ValueRef ((if all P.isSymbolChar ident then P.Op else P.Ident) ident)
224+
225+ -- | Adds a declaration to an import:
226+ -- TypeDeclaration "Maybe" + Data.Maybe (maybe) -> Data.Maybe(Maybe, maybe)
227+ insertDeclIntoImport :: ExternDecl -> Import -> Import
228+ insertDeclIntoImport decl' (Import mn (P.Explicit refs) Nothing) =
229+ Import mn (P.Explicit (insertDeclIntoRefs decl' refs)) Nothing
230+ insertDeclIntoImport _ is = is
231+
232+ insertDeclIntoRefs :: ExternDecl -> [P.DeclarationRef] -> [P.DeclarationRef]
233+ insertDeclIntoRefs (DataConstructor dtor tn _) refs =
234+ let
235+ dtor' = P.ProperName (T.unpack dtor)
236+ -- TODO: Get rid of this once typeclasses can't be imported like types
237+ refs' = properRefToTypeRef <$> refs
238+ in
239+ updateAtFirstOrPrepend (matchType tn) (insertDtor dtor') (P.TypeRef tn (Just [dtor'])) refs'
240+ insertDeclIntoRefs dr refs = List.nubBy ((==) `on` P.prettyPrintRef) (refFromDeclaration dr : refs)
241+
242+ insertDtor dtor (P.TypeRef tn' dtors) =
243+ case dtors of
244+ Just dtors' -> P.TypeRef tn' (Just (List.nub (dtor : dtors')))
245+ -- This means the import was opened. We don't add anything in this case
246+ -- import Data.Maybe (Maybe(..)) -> import Data.Maybe (Maybe(Just))
247+ Nothing -> P.TypeRef tn' Nothing
248+ insertDtor _ refs = refs
249+
250+
251+ -- TODO: Get rid of this once typeclasses can't be imported like types
252+ properRefToTypeRef (P.ProperRef n) = P.TypeRef (P.ProperName n) (Just [])
253+ properRefToTypeRef r = r
254+
255+ matchType :: P.ProperName 'P.TypeName -> P.DeclarationRef -> Bool
256+ matchType tn (P.TypeRef n _) = tn == n
257+ matchType _ _ = False
258+
259+updateAtFirstOrPrepend :: (a -> Bool) -> (a -> a) -> a -> [a] -> [a]
260+updateAtFirstOrPrepend p t d l =
261+ case List.findIndex p l of
262+ Nothing -> d : l
263+ Just ix ->
264+ let (x, a : y) = List.splitAt ix l
265+ in x ++ [t a] ++ y
266+
267+-- | Looks up the given identifier in the currently loaded modules.
268+--
269+-- * Throws an error if the identifier cannot be found.
270+--
271+-- * If exactly one match is found, adds an explicit import to the importsection
272+--
273+-- * If more than one possible imports are found, reports the possibilities as a
274+-- list of completions.
275+addImportForIdentifier :: (PscIde m, MonadError PscIdeError m, MonadLogger m)
276+ => FilePath -- ^ The Sourcefile to read from
277+ -> Text -- ^ The identifier to import
278+ -> [Filter] -- ^ Filters to apply before searching for
279+ -- the identifier
280+ -> m (Either [Match] [Text])
281+addImportForIdentifier fp ident filters = do
282+ modules <- getAllModulesWithReexports
283+ case getExactMatches ident filters modules of
284+ [] ->
285+ throwError (NotFound "Couldn't find the given identifier. \
286+ \Have you loaded the corresponding module?")
287+
288+ -- Only one match was found for the given identifier, so we can insert it
289+ -- right away
290+ [Match m decl] ->
291+ Right <$> addExplicitImport fp decl (P.moduleNameFromString (T.unpack m))
292+
293+ -- This case comes up for newtypes and dataconstructors. Because values and
294+ -- types don't share a namespace we can get multiple matches from the same
295+ -- module. This also happens for parameterized types, as these generate both
296+ -- a type aswell as a type synonym.
297+
298+ ms@[Match m1 d1, Match m2 d2] ->
299+ if m1 /= m2
300+ -- If the modules don't line up we just ask the user to specify the
301+ -- module
302+ then pure $ Left ms
303+ else case decideRedundantCase d1 d2 <|> decideRedundantCase d2 d1 of
304+ -- If dataconstructor and type line up we just import the
305+ -- dataconstructor as that will give us an unnecessary import warning at
306+ -- worst
307+ Just decl ->
308+ Right <$> addExplicitImport fp decl (P.moduleNameFromString (T.unpack m1))
309+ -- Here we need the user to specify whether he wanted a dataconstructor
310+ -- or a type
311+ Nothing ->
312+ throwError (GeneralError "Undecidable between type and dataconstructor")
313+
314+ -- Multiple matches were found so we need to ask the user to clarify which
315+ -- module he meant
316+ xs ->
317+ pure $ Left xs
318+ where
319+ decideRedundantCase dtor@(DataConstructor _ t _) (TypeDeclaration t' _) =
320+ if t == t' then Just dtor else Nothing
321+ decideRedundantCase TypeDeclaration{} ts@TypeSynonymDeclaration{} =
322+ Just ts
323+ decideRedundantCase _ _ = Nothing
324+
325+prettyPrintImport' :: Import -> Text
326+-- TODO: remove this clause once P.prettyPrintImport can properly handle PositionedRefs
327+prettyPrintImport' (Import mn (P.Explicit refs) qual) =
328+ T.pack $ "import " ++ P.prettyPrintImport mn (P.Explicit (unwrapPositionedRef <$> refs)) qual
329+prettyPrintImport' (Import mn idt qual) =
330+ T.pack $ "import " ++ P.prettyPrintImport mn idt qual
331+
332+prettyPrintImportSection :: [Import] -> [Text]
333+prettyPrintImportSection imports = map prettyPrintImport' (List.sort imports)
334+
335+-- | Writes a list of lines to @Just filepath@ and responds with a @TextResult@,
336+-- or returns the lines as a @MultilineTextResult@ if @Nothing@ was given as the
337+-- first argument.
338+answerRequest :: (MonadIO m) => Maybe FilePath -> [Text] -> m Success
339+answerRequest outfp rs =
340+ case outfp of
341+ Nothing -> pure $ MultilineTextResult rs
342+ Just outfp' -> do
343+ liftIO $ TIO.writeFile outfp' (T.unlines rs)
344+ pure $ TextResult $ "Written to " <> T.pack outfp'
345+
346+-- | Test and ghci helper
347+parseImport :: Text -> Maybe Import
348+parseImport t =
349+ case P.lex "<psc-ide>" (T.unpack t)
350+ >>= P.runTokenParser "<psc-ide>" P.parseImportDeclaration' of
351+ Right (mn, P.Explicit refs, mmn, _) ->
352+ Just (Import mn (P.Explicit (unwrapPositionedRef <$> refs)) mmn)
353+ Right (mn, idt, mmn, _) -> Just (Import mn idt mmn)
354+ Left _ -> Nothing
355+
src/Language/PureScript/Ide/Matcher.hs+46−25
@@ -1,7 +1,26 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Matcher
4+-- Description : Matchers for psc-ide commands
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Matchers for psc-ide commands
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE GeneralizedNewtypeDeriving #-}
216{-# LANGUAGE OverloadedStrings #-}
317{-# LANGUAGE ScopedTypeVariables #-}
4−module Language.PureScript.Ide.Matcher (Matcher, flexMatcher, runMatcher) where
18+
19+module Language.PureScript.Ide.Matcher
20+ ( Matcher
21+ , flexMatcher
22+ , runMatcher
23+ ) where
524
625import Prelude ()
726import Prelude.Compat
@@ -16,13 +35,14 @@ import Data.Text (Text)
1635import qualified Data.Text as T
1736import qualified Data.Text.Encoding as TE
1837import Language.PureScript.Ide.Types
38+import Language.PureScript.Ide.Util
1939import Text.EditDistance
2040import Text.Regex.TDFA ((=~))
2141
2242
23−type ScoredCompletion = (Completion, Double)
43+type ScoredMatch = (Match, Double)
2444
25−newtype Matcher = Matcher (Endo [Completion]) deriving(Monoid)
45+newtype Matcher = Matcher (Endo [Match]) deriving(Monoid)
2646
2747instance FromJSON Matcher where
2848 parseJSON = withObject "matcher" $ \o -> do
@@ -41,42 +61,43 @@ instance FromJSON Matcher where
4161 Nothing -> return mempty
4262
4363-- | 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
64+--
65+-- The scoring measures how far the matches span the string where
66+-- closer is better.
67+-- Examples:
68+-- flMa matches flexMatcher. Score: 14.28
69+-- sons matches sortCompletions. Score: 6.25
5070flexMatcher :: Text -> Matcher
51−flexMatcher pattern = mkMatcher (flexMatch pattern)
71+flexMatcher p = mkMatcher (flexMatch p)
5272
5373distanceMatcher :: Text -> Int -> Matcher
5474distanceMatcher q maxDist = mkMatcher (distanceMatcher' q maxDist)
5575
56−distanceMatcher' :: Text -> Int -> [Completion] -> [ScoredCompletion]
76+distanceMatcher' :: Text -> Int -> [Match] -> [ScoredMatch]
5777distanceMatcher' q maxDist = mapMaybe go
5878 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
79+ go m = let d = dist (T.unpack y)
80+ y = identifierFromMatch m
81+ in if d <= maxDist
82+ then Just (m, 1 / fromIntegral d)
83+ else Nothing
6384 dist = levenshteinDistance defaultEditCosts (T.unpack q)
6485
65−mkMatcher :: ([Completion] -> [ScoredCompletion]) -> Matcher
86+mkMatcher :: ([Match] -> [ScoredMatch]) -> Matcher
6687mkMatcher matcher = Matcher . Endo $ fmap fst . sortCompletions . matcher
6788
68−runMatcher :: Matcher -> [Completion] -> [Completion]
89+runMatcher :: Matcher -> [Match] -> [Match]
6990runMatcher (Matcher m)= appEndo m
7091
71−sortCompletions :: [ScoredCompletion] -> [ScoredCompletion]
92+sortCompletions :: [ScoredMatch] -> [ScoredMatch]
7293sortCompletions = sortBy (flip compare `on` snd)
7394
74−flexMatch :: Text -> [Completion] -> [ScoredCompletion]
75−flexMatch pattern = mapMaybe (flexRate pattern)
95+flexMatch :: Text -> [Match] -> [ScoredMatch]
96+flexMatch = mapMaybe . flexRate
7697
77−flexRate :: Text -> Completion -> Maybe ScoredCompletion
78−flexRate pattern c@(Completion (_,ident,_)) = do
79− score <- flexScore pattern ident
98+flexRate :: Text -> Match -> Maybe ScoredMatch
99+flexRate p c = do
100+ score <- flexScore p (identifierFromMatch c)
80101 return (c, score)
81102
82103-- FlexMatching ala Sublime.
@@ -89,13 +110,13 @@ flexScore :: Text -> DeclIdent -> Maybe Double
89110flexScore pat str =
90111 case T.uncons pat of
91112 Nothing -> Nothing
92− Just (first, pattern) ->
113+ Just (first, p) ->
93114 case TE.encodeUtf8 str =~ TE.encodeUtf8 pat' :: (Int, Int) of
94115 (-1,0) -> Nothing
95116 (start,len) -> Just $ calcScore start (start + len)
96117 where
97118 escapedPattern :: [Text]
98− escapedPattern = map escape (T.unpack pattern)
119+ escapedPattern = map escape (T.unpack p)
99120
100121 -- escape prepends a backslash to "regexy" characters to prevent the
101122 -- matcher from crashing when trying to build the regex
src/Language/PureScript/Ide/Pursuit.hs+14
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Pursuit
4+-- Description : Pursuit client for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Pursuit client for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE OverloadedStrings #-}
216{-# LANGUAGE ScopedTypeVariables #-}
317
src/Language/PureScript/Ide/Reexports.hs+16
@@ -1,6 +1,22 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Reexports
4+-- Description : Resolves reexports for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- Brian Sermons 2016
7+-- License : MIT (http://opensource.org/licenses/MIT)
8+--
9+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
10+-- Stability : experimental
11+--
12+-- |
13+-- Resolves reexports for psc-ide
14+-----------------------------------------------------------------------------
15+
116{-# LANGUAGE OverloadedStrings #-}
217{-# LANGUAGE PatternGuards #-}
318{-# LANGUAGE TupleSections #-}
19+
420module Language.PureScript.Ide.Reexports where
521
622
src/Language/PureScript/Ide/SourceFile.hs+16−1
@@ -1,6 +1,21 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.SourceFile
4+-- Description : Getting declarations from PureScript sourcefiles
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Getting declarations from PureScript sourcefiles
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE FlexibleContexts #-}
216{-# LANGUAGE MultiParamTypeClasses #-}
317{-# LANGUAGE OverloadedStrings #-}
18+
419module Language.PureScript.Ide.SourceFile where
520
621import Prelude
@@ -69,7 +84,7 @@ getPositionedImports :: D.Module -> [D.Declaration]
6984getPositionedImports (D.Module _ _ _ declarations _) =
7085 mapMaybe isImport declarations
7186 where
72− isImport i@(D.PositionedDeclaration _ _ (D.ImportDeclaration{})) = Just i
87+ isImport i@(D.PositionedDeclaration _ _ D.ImportDeclaration{}) = Just i
7388 isImport _ = Nothing
7489
7590getDeclPosition :: D.Module -> String -> Maybe SP.SourceSpan
src/Language/PureScript/Ide/State.hs+21−6
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.State
4+-- Description : Functions to access psc-ide's state
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Functions to access psc-ide's state
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE MultiParamTypeClasses #-}
@@ -68,12 +82,13 @@ insertModule ::(PscIde m, MonadLogger m) =>
6882insertModule externsFile = do
6983 env <- ask
7084 let moduleName = efModuleName externsFile
71− $(logDebug) $ "Inserting Module: " <> (T.pack (runModuleName moduleName))
85+ $(logDebug) $ "Inserting Module: " <> T.pack (runModuleName moduleName)
7286 liftIO . atomically $ insertModule' (envStateVar env) externsFile
7387
7488insertModule' :: TVar PscIdeState -> ExternsFile -> STM ()
75−insertModule' st ef = modifyTVar st $ \x ->
76− x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
77− , pscStateModules = let (mn, decls) = convertExterns ef
78− in M.insert mn decls (pscStateModules x)
79− }
89+insertModule' st ef =
90+ modifyTVar st $ \x ->
91+ x { externsFiles = M.insert (efModuleName ef) ef (externsFiles x)
92+ , pscStateModules = let (mn, decls) = convertExterns ef
93+ in M.insert mn decls (pscStateModules x)
94+ }
src/Language/PureScript/Ide/Types.hs+46−48
@@ -1,3 +1,17 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Types
4+-- Description : Type definitions for psc-ide
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Type definitions for psc-ide
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE ConstraintKinds #-}
216{-# LANGUAGE FlexibleContexts #-}
317{-# LANGUAGE GeneralizedNewtypeDeriving #-}
@@ -19,51 +33,40 @@ import Data.Maybe (maybeToList)
1933import Data.Text (Text (), pack, unpack)
2034import qualified Language.PureScript.AST.Declarations as D
2135import Language.PureScript.Externs
22−import Language.PureScript.Names
2336import qualified Language.PureScript.Names as N
37+import qualified Language.PureScript as P
2438
2539import Text.Parsec
2640import Text.Parsec.Text
2741
42+type Ident = Text
43+type DeclIdent = Text
2844type ModuleIdent = Text
29−type DeclIdent = Text
30−type Type = Text
31−
32−data Fixity = Infix | Infixl | Infixr deriving(Show, Eq, Ord)
3345
3446data ExternDecl
3547 -- | A function/value declaration
36− = FunctionDecl
37− DeclIdent -- The functions name
38− Type -- The functions type
39− | FixityDeclaration Fixity Int DeclIdent
48+ = ValueDeclaration Ident P.Type
49+ | TypeDeclaration (P.ProperName 'P.TypeName) P.Kind
50+ | TypeSynonymDeclaration (P.ProperName 'P.TypeName) P.Type
4051 -- | A Dependency onto another Module
4152 | Dependency
4253 ModuleIdent -- name of the dependency
4354 [Text] -- explicit imports
4455 (Maybe Text) -- An eventual qualifier
45−
4656 -- | A module declaration
4757 | ModuleDecl
4858 ModuleIdent -- The modules name
4959 [DeclIdent] -- The exported identifiers
5060 -- | A data/newtype declaration
51− | DataDecl DeclIdent -- The type name
52− Text -- The "type"
61+ | DataConstructor
62+ DeclIdent -- ^ The type name
63+ (P.ProperName 'P.TypeName)
64+ P.Type -- ^ The "type"
5365 -- | An exported module
66+ | TypeClassDeclaration (P.ProperName 'P.ClassName)
5467 | Export ModuleIdent -- The exported Modules name
5568 deriving (Show,Eq,Ord)
5669
57−instance ToJSON ExternDecl where
58− toJSON (FunctionDecl n t) = object ["name" .= n, "type" .= t]
59− toJSON (ModuleDecl n t) = object ["name" .= n, "type" .= t]
60− toJSON (DataDecl n t) = object ["name" .= n, "type" .= t]
61− toJSON (Dependency n names _) = object ["module" .= n, "names" .= names]
62− toJSON (FixityDeclaration f p n) = object ["name" .= n
63− , "fixity" .= show f
64− , "precedence" .= p]
65− toJSON (Export _) = object []
66−
6770type Module = (ModuleIdent, [ExternDecl])
6871
6972data Configuration =
@@ -83,15 +86,22 @@ type PscIde m = (MonadIO m, MonadReader PscIdeEnvironment m)
8386data PscIdeState =
8487 PscIdeState
8588 { pscStateModules :: M.Map Text [ExternDecl]
86− , externsFiles :: M.Map ModuleName ExternsFile
89+ , externsFiles :: M.Map P.ModuleName ExternsFile
8790 } deriving Show
8891
8992emptyPscIdeState :: PscIdeState
9093emptyPscIdeState = PscIdeState M.empty M.empty
9194
95+data Match = Match ModuleIdent ExternDecl
96+ deriving (Show, Eq)
97+
9298newtype Completion =
93− Completion (ModuleIdent, DeclIdent, Type)
94− deriving (Show,Eq)
99+ Completion (ModuleIdent, DeclIdent, Text)
100+ deriving (Show,Eq)
101+
102+instance ToJSON Completion where
103+ toJSON (Completion (m,d,t)) =
104+ object ["module" .= m, "identifier" .= d, "type" .= t]
95105
96106data ModuleImport =
97107 ModuleImport
@@ -127,18 +137,6 @@ identifierFromDeclarationRef (D.ValueRef ident) = N.runIdent ident
127137identifierFromDeclarationRef (D.TypeClassRef name) = N.runProperName name
128138identifierFromDeclarationRef _ = ""
129139
130−instance FromJSON Completion where
131− parseJSON (Object o) = do
132− m <- o .: "module"
133− d <- o .: "identifier"
134− t <- o .: "type"
135− pure (Completion (m, d, t))
136− parseJSON _ = mzero
137−
138−instance ToJSON Completion where
139− toJSON (Completion (m,d,t)) =
140− object ["module" .= m, "identifier" .= d, "type" .= t]
141−
142140data Success =
143141 CompletionResult [Completion]
144142 | TextResult Text
@@ -174,7 +172,7 @@ instance FromJSON PursuitSearchType where
174172 parseJSON _ = mzero
175173
176174instance FromJSON PursuitQuery where
177− parseJSON o = PursuitQuery <$> (parseJSON o)
175+ parseJSON o = PursuitQuery <$> parseJSON o
178176
179177data PursuitResponse =
180178 -- | A Pursuit Response for a module. Consists of the modules name and the
@@ -182,7 +180,7 @@ data PursuitResponse =
182180 ModuleResponse ModuleIdent Text
183181 -- | A Pursuit Response for a declaration. Consist of the declarations type,
184182 -- module, name and package
185− | DeclarationResponse Type ModuleIdent DeclIdent Text
183+ | DeclarationResponse Text ModuleIdent DeclIdent Text
186184 deriving (Show,Eq)
187185
188186instance FromJSON PursuitResponse where
@@ -215,15 +213,15 @@ typeParse t = case parse parseType "" t of
215213 type' <- many1 anyChar
216214 pure (unpack name, type')
217215
218−identifier :: Parser Text
219−identifier = do
220− spaces
221− ident <-
222− -- necessary for being able to parse the following ((++), concat)
223− between (char '(') (char ')') (many1 (noneOf ", )")) <|>
224− many1 (noneOf ", )")
225− spaces
226− pure (pack ident)
216+ identifier :: Parser Text
217+ identifier = do
218+ spaces
219+ ident <-
220+ -- necessary for being able to parse the following ((++), concat)
221+ between (char '(') (char ')') (many1 (noneOf ", )")) <|>
222+ many1 (noneOf ", )")
223+ spaces
224+ pure (pack ident)
227225
228226instance ToJSON PursuitResponse where
229227 toJSON (ModuleResponse name package) =
src/Language/PureScript/Ide/Util.hsnew file+65
@@ -0,0 +1,65 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Util
4+-- Description : Generally useful functions and conversions
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- Generally useful functions and conversions
13+-----------------------------------------------------------------------------
14+
15+{-# LANGUAGE OverloadedStrings #-}
16+
17+module Language.PureScript.Ide.Util where
18+
19+import Data.Aeson
20+import Data.Text (Text)
21+import qualified Data.Text as T
22+import Data.Text.Lazy (fromStrict, toStrict)
23+import Data.Text.Lazy.Encoding (decodeUtf8, encodeUtf8)
24+import qualified Language.PureScript as P
25+import Language.PureScript.Ide.Types
26+
27+runProperNameT :: P.ProperName a -> Text
28+runProperNameT = T.pack . P.runProperName
29+
30+runIdentT :: P.Ident -> Text
31+runIdentT = T.pack . P.runIdent
32+
33+prettyTypeT :: P.Type -> Text
34+prettyTypeT = T.unwords . fmap T.strip . T.lines . T.pack . P.prettyPrintType
35+
36+identifierFromExternDecl :: ExternDecl -> Text
37+identifierFromExternDecl (ValueDeclaration name _) = name
38+identifierFromExternDecl (TypeDeclaration name _) = runProperNameT name
39+identifierFromExternDecl (TypeSynonymDeclaration name _) = runProperNameT name
40+identifierFromExternDecl (DataConstructor name _ _) = name
41+identifierFromExternDecl (TypeClassDeclaration name) = runProperNameT name
42+identifierFromExternDecl (ModuleDecl name _) = name
43+identifierFromExternDecl Dependency{} = "~Dependency~"
44+identifierFromExternDecl Export{} = "~Export~"
45+
46+identifierFromMatch :: Match -> Text
47+identifierFromMatch (Match _ ed) = identifierFromExternDecl ed
48+
49+completionFromMatch :: Match -> Maybe Completion
50+completionFromMatch (Match _ Dependency{}) = Nothing
51+completionFromMatch (Match _ Export{}) = Nothing
52+completionFromMatch (Match m d) = Just $ case d of
53+ ValueDeclaration name type' -> Completion (m, name, prettyTypeT type')
54+ TypeDeclaration name kind -> Completion (m, runProperNameT name, T.pack $ P.prettyPrintKind kind)
55+ TypeSynonymDeclaration name kind -> Completion (m, runProperNameT name, prettyTypeT kind)
56+ DataConstructor name _ type' -> Completion (m, name, prettyTypeT type')
57+ TypeClassDeclaration name -> Completion (m, runProperNameT name, "class")
58+ ModuleDecl name _ -> Completion ("module", name, "module")
59+ _ -> error "the impossible happened in completionFromMatch"
60+
61+encodeT :: (ToJSON a) => a -> Text
62+encodeT = toStrict . decodeUtf8 . encode
63+
64+decodeT :: (FromJSON a) => Text -> Maybe a
65+decodeT = decode . encodeUtf8 . fromStrict
src/Language/PureScript/Ide/Watcher.hs+15
@@ -1,4 +1,19 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Watcher
4+-- Description : File watcher for externs files
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- File watcher for externs files
13+-----------------------------------------------------------------------------
14+
115{-# LANGUAGE RecordWildCards #-}
16+
217module Language.PureScript.Ide.Watcher where
318
419import Prelude ()
tests/Language/PureScript/Ide/FilterSpec.hs+7−3
@@ -4,15 +4,19 @@ module Language.PureScript.Ide.FilterSpec where
44import Data.Text (Text)
55import Language.PureScript.Ide.Filter
66import Language.PureScript.Ide.Types
7+import qualified Language.PureScript as P
78import Test.Hspec
89
10+value :: Text -> ExternDecl
11+value s = ValueDeclaration s P.TypeWildcard
12+
913modules :: [Module]
1014modules =
1115 [
12− ("Module.A", [FunctionDecl "function1" ""]),
13− ("Module.B", [DataDecl "data1" ""]),
16+ ("Module.A", [value "function1"]),
17+ ("Module.B", [value "data1"]),
1418 ("Module.C", [ModuleDecl "Module.C" []]),
15− ("Module.D", [Dependency "Module.C" [] Nothing, FunctionDecl "asd" ""])
19+ ("Module.D", [Dependency "Module.C" [] Nothing, value "asd"])
1620 ]
1721
1822runEq :: Text -> [Module]
tests/Language/PureScript/Ide/Imports/IntegrationSpec.hsnew file+101
@@ -0,0 +1,101 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.Imports.IntegrationSpec where
3+
4+import Control.Monad
5+import Data.Text (Text)
6+import qualified Data.Text as T
7+import qualified Data.Text.IO as TIO
8+import qualified Language.PureScript.Ide.Integration as Integration
9+import Test.Hspec
10+
11+import System.Directory
12+import System.FilePath
13+
14+setup :: IO ()
15+setup = do
16+ Integration.deleteOutputFolder
17+ s <- Integration.compileTestProject
18+ unless s $ fail "Failed to compile .purs sources"
19+ Integration.quitServer -- kill a eventually running psc-ide-server instance
20+ _ <- Integration.startServer
21+ mapM_ Integration.loadModuleWithDeps ["ImportsSpec", "ImportsSpec1"]
22+
23+teardown :: IO ()
24+teardown = Integration.quitServer
25+
26+withSupportFiles :: (FilePath -> FilePath -> IO a) -> IO ()
27+withSupportFiles test = do
28+ pdir <- Integration.projectDirectory
29+ let sourceFp = pdir </> "src" </> "ImportsSpec.purs"
30+ outFp = pdir </> "src" </> "ImportsSpecOut.tmp"
31+ Integration.deleteFileIfExists outFp
32+ void $ test sourceFp outFp
33+
34+outputFileShouldBe :: [Text] -> IO ()
35+outputFileShouldBe expectation = do
36+ outFp <- (</> "src" </> "ImportsSpecOut.tmp") <$> Integration.projectDirectory
37+ outRes <- TIO.readFile outFp
38+ shouldBe (T.lines outRes) expectation
39+
40+spec :: Spec
41+spec = beforeAll_ setup $ afterAll_ teardown $ describe "Adding imports" $ do
42+ let
43+ sourceFileSkeleton :: [Text] -> [Text]
44+ sourceFileSkeleton importSection =
45+ [ "module ImportsSpec where" , ""] ++ importSection ++ [ "" , "myId = id"]
46+ it "adds an implicit import" $ do
47+ withSupportFiles (Integration.addImplicitImport "Prelude")
48+ outputFileShouldBe (sourceFileSkeleton
49+ [ "import Prelude"
50+ , "import Main (id)"
51+ ])
52+ it "adds an explicit unqualified import" $ do
53+ withSupportFiles (Integration.addImport "exportedFunction")
54+ outputFileShouldBe (sourceFileSkeleton
55+ [ "import ImportsSpec1 (exportedFunction)"
56+ , "import Main (id)"
57+ ])
58+ it "adds an explicit unqualified import (type)" $ do
59+ withSupportFiles (Integration.addImport "MyType")
60+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyType)"
61+ , "import Main (id)"
62+ ])
63+ it "adds an explicit unqualified import (parameterized type)" $ do
64+ withSupportFiles (Integration.addImport "MyParamType")
65+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyParamType)"
66+ , "import Main (id)"
67+ ])
68+ it "adds an explicit unqualified import (typeclass)" $ do
69+ withSupportFiles (Integration.addImport "ATypeClass")
70+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (class ATypeClass)"
71+ , "import Main (id)"])
72+ it "adds an explicit unqualified import (dataconstructor)" $ do
73+ withSupportFiles (Integration.addImport "MyJust")
74+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyMaybe(MyJust))"
75+ , "import Main (id)"])
76+ it "adds an explicit unqualified import (newtype)" $ do
77+ withSupportFiles (Integration.addImport "MyNewtype")
78+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (MyNewtype(MyNewtype))"
79+ , "import Main (id)"])
80+ it "adds an explicit unqualified import (typeclass member function)" $ do
81+ withSupportFiles (Integration.addImport "typeClassFun")
82+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (typeClassFun)"
83+ , "import Main (id)"])
84+ it "doesn't add a newtypes constructor if only the type is exported" $ do
85+ withSupportFiles (Integration.addImport "OnlyTypeExported")
86+ outputFileShouldBe (sourceFileSkeleton [ "import ImportsSpec1 (OnlyTypeExported)"
87+ , "import Main (id)"])
88+ it "doesn't add an import if the identifier is defined in the module itself" $ do
89+ withSupportFiles (Integration.addImport "myId")
90+ outputFileShouldBe (sourceFileSkeleton [ "import Main (id)"])
91+ it "responds with an error if it's undecidable whether we want a type or constructor" $
92+ withSupportFiles (\sourceFp outFp -> do
93+ r <- Integration.addImport "SpecialCase" sourceFp outFp
94+ shouldBe False (Integration.resultIsSuccess r)
95+ shouldBe False =<< doesFileExist outFp)
96+ it "responds with an error if the identifier cannot be found and doesn't \
97+ \write to the output file" $
98+ withSupportFiles (\sourceFp outFp -> do
99+ r <- Integration.addImport "doesntExist" sourceFp outFp
100+ shouldBe False (Integration.resultIsSuccess r)
101+ shouldBe False =<< doesFileExist outFp)
tests/Language/PureScript/Ide/ImportsSpec.hsnew file+125
@@ -0,0 +1,125 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+module Language.PureScript.Ide.ImportsSpec where
3+
4+import Data.Maybe (fromJust)
5+import Data.Text (Text)
6+import qualified Language.PureScript as P
7+import Language.PureScript.Ide.Imports
8+import Language.PureScript.Ide.Types
9+import Test.Hspec
10+
11+simpleFile :: [Text]
12+simpleFile =
13+ [ "module Main where"
14+ , "import Prelude"
15+ , ""
16+ , "myFunc x y = x + y"
17+ ]
18+
19+splitSimpleFile :: (P.ModuleName, [Text], [Import], [Text])
20+splitSimpleFile = fromRight $ sliceImportSection simpleFile
21+ where
22+ fromRight (Right r) = r
23+ fromRight (Left _) = error "fromRight"
24+
25+withImports :: [Text] -> [Text]
26+withImports is =
27+ take 2 simpleFile ++ is ++ drop 2 simpleFile
28+
29+testParseImport :: Text -> Import
30+testParseImport = fromJust . parseImport
31+
32+preludeImport, arrayImport, listImport, consoleImport, maybeImport :: Import
33+preludeImport = testParseImport "import Prelude"
34+arrayImport = testParseImport "import Data.Array (head, cons)"
35+listImport = testParseImport "import Data.List as List"
36+consoleImport = testParseImport "import Control.Monad.Eff.Console (log) as Console"
37+maybeImport = testParseImport "import Data.Maybe (Maybe(Just))"
38+
39+spec :: Spec
40+spec = do
41+ describe "determining the importsection" $ do
42+ let moduleSkeleton imports =
43+ Right (P.moduleNameFromString "Main", take 1 simpleFile, imports, drop 2 simpleFile)
44+ it "finds a simple import" $
45+ shouldBe (sliceImportSection simpleFile) (moduleSkeleton [preludeImport])
46+
47+ it "allows multiline import statements" $
48+ shouldBe
49+ (sliceImportSection (withImports [ "import Data.Array (head,"
50+ , " cons)"
51+ ]))
52+ (moduleSkeleton [preludeImport, arrayImport])
53+ describe "pretty printing imports" $ do
54+ it "pretty prints a simple import" $
55+ shouldBe (prettyPrintImport' preludeImport) "import Prelude"
56+ it "pretty prints an explicit import" $
57+ shouldBe (prettyPrintImport' arrayImport) "import Data.Array (head, cons)"
58+ it "pretty prints a qualified import" $
59+ shouldBe (prettyPrintImport' listImport) "import Data.List as List"
60+ it "pretty prints a qualified explicit import" $
61+ shouldBe (prettyPrintImport' consoleImport) "import Control.Monad.Eff.Console (log) as Console"
62+ it "pretty prints an import with a datatype (and PositionedRef's for the dtors)" $
63+ shouldBe (prettyPrintImport' maybeImport) "import Data.Maybe (Maybe(Just))"
64+
65+ describe "import commands" $ do
66+ let simpleFileImports = let (_, _, i, _) = splitSimpleFile in i
67+ addValueImport i mn is =
68+ prettyPrintImportSection (addExplicitImport' (ValueDeclaration i P.TypeWildcard) mn is)
69+ addDtorImport i t mn is =
70+ prettyPrintImportSection (addExplicitImport' (DataConstructor i t P.TypeWildcard) mn is)
71+ it "adds an implicit unqualified import" $
72+ shouldBe
73+ (addImplicitImport' simpleFileImports (P.moduleNameFromString "Data.Map"))
74+ [ "import Prelude"
75+ , "import Data.Map"
76+ ]
77+ it "adds an explicit unqualified import" $
78+ shouldBe
79+ (addValueImport "head" (P.moduleNameFromString "Data.Array") simpleFileImports)
80+ [ "import Prelude"
81+ , "import Data.Array (head)"
82+ ]
83+ it "doesn't add an import if the containing module is imported implicitly" $
84+ shouldBe
85+ (addValueImport "const" (P.moduleNameFromString "Prelude") simpleFileImports)
86+ ["import Prelude"]
87+ let Right (_, _, explicitImports, _) = sliceImportSection (withImports ["import Data.Array (tail)"])
88+ it "adds an identifier to an explicit import list" $
89+ shouldBe
90+ (addValueImport "head" (P.moduleNameFromString "Data.Array") explicitImports)
91+ [ "import Prelude"
92+ , "import Data.Array (head, tail)"
93+ ]
94+ it "adds an operator to an explicit import list" $
95+ shouldBe
96+ (addValueImport "<~>" (P.moduleNameFromString "Data.Array") explicitImports)
97+ [ "import Prelude"
98+ , "import Data.Array ((<~>), tail)"
99+ ]
100+ it "adds the type for a given DataConstructor" $
101+ shouldBe
102+ (addDtorImport "Just" (P.ProperName "Maybe") (P.moduleNameFromString "Data.Maybe") simpleFileImports)
103+ [ "import Prelude"
104+ , "import Data.Maybe (Maybe(Just))"
105+ ]
106+ it "adds a dataconstructor to an existing type import" $ do
107+ let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe)"])
108+ shouldBe
109+ (addDtorImport "Just" (P.ProperName "Maybe") (P.moduleNameFromString "Data.Maybe") typeImports)
110+ [ "import Prelude"
111+ , "import Data.Maybe (Maybe(Just))"
112+ ]
113+ it "doesn't add a dataconstructor to an existing type import with open dtors" $ do
114+ let Right (_, _, typeImports, _) = sliceImportSection (withImports ["import Data.Maybe (Maybe(..))"])
115+ shouldBe
116+ (addDtorImport "Just" (P.ProperName "Maybe") (P.moduleNameFromString "Data.Maybe") typeImports)
117+ [ "import Prelude"
118+ , "import Data.Maybe (Maybe(..))"
119+ ]
120+ it "doesn't add an identifier to an explicit import list if it's already imported" $
121+ shouldBe
122+ (addValueImport "tail" (P.moduleNameFromString "Data.Array") explicitImports)
123+ [ "import Prelude"
124+ , "import Data.Array (tail)"
125+ ]
tests/Language/PureScript/Ide/Integration.hsnew file+238
@@ -0,0 +1,238 @@
1+-----------------------------------------------------------------------------
2+--
3+-- Module : Language.PureScript.Ide.Integration
4+-- Description : A psc-ide client for use in integration tests
5+-- Copyright : Christoph Hegemann 2016
6+-- License : MIT (http://opensource.org/licenses/MIT)
7+--
8+-- Maintainer : Christoph Hegemann <christoph.hegemann1337@gmail.com>
9+-- Stability : experimental
10+--
11+-- |
12+-- A psc-ide client for use in integration tests
13+-----------------------------------------------------------------------------
14+
15+{-# LANGUAGE OverloadedStrings #-}
16+{-# LANGUAGE ScopedTypeVariables #-}
17+module Language.PureScript.Ide.Integration
18+ (
19+ -- managing the server process
20+ startServer
21+ , withServer
22+ , stopServer
23+ , quitServer
24+ -- util
25+ , compileTestProject
26+ , deleteOutputFolder
27+ , projectDirectory
28+ , deleteFileIfExists
29+ -- sending commands
30+ , loadModuleWithDeps
31+ , getFlexCompletions
32+ , getType
33+ , addImport
34+ , addImplicitImport
35+ -- checking results
36+ , resultIsSuccess
37+ , parseCompletions
38+ , parseTextResult
39+ ) where
40+
41+import Control.Concurrent (threadDelay)
42+import Control.Exception
43+import Control.Monad (join, when)
44+import Data.Aeson
45+import Data.Aeson.Types
46+import qualified Data.ByteString.Lazy.UTF8 as BSL
47+import Data.Either (isRight)
48+import Data.Maybe (fromJust)
49+import qualified Data.Text as T
50+import qualified Data.Vector as V
51+import Language.PureScript.Ide.Util
52+import System.Directory
53+import System.Exit
54+import System.FilePath
55+import System.Process
56+
57+projectDirectory :: IO FilePath
58+projectDirectory = do
59+ cd <- getCurrentDirectory
60+ return $ cd </> "tests" </> "support" </> "pscide"
61+
62+startServer :: IO ProcessHandle
63+startServer = do
64+ pdir <- projectDirectory
65+ (_, _, _, procHandle) <- createProcess $ (shell "psc-ide-server") {cwd=Just pdir}
66+ threadDelay 500000 -- give the server 500ms to start up
67+ return procHandle
68+
69+stopServer :: ProcessHandle -> IO ()
70+stopServer = terminateProcess
71+
72+withServer :: IO a -> IO a
73+withServer s = do
74+ _ <- startServer
75+ r <- s
76+ quitServer
77+ return r
78+
79+-- project management utils
80+
81+compileTestProject :: IO Bool
82+compileTestProject = do
83+ pdir <- projectDirectory
84+ (_, _, _, procHandle) <- createProcess $
85+ (shell $ "psc " ++ fileGlob) {cwd=Just pdir
86+ ,std_out=CreatePipe
87+ ,std_err=CreatePipe
88+ }
89+ isSuccess <$> waitForProcess procHandle
90+
91+deleteOutputFolder :: IO ()
92+deleteOutputFolder = do
93+ odir <- fmap (</> "output") projectDirectory
94+ whenM (doesDirectoryExist odir) (removeDirectoryRecursive odir)
95+
96+deleteFileIfExists :: FilePath -> IO ()
97+deleteFileIfExists fp = whenM (doesFileExist fp) (removeFile fp)
98+
99+whenM :: Monad m => m Bool -> m () -> m ()
100+whenM p f = do
101+ x <- p
102+ when x f
103+
104+isSuccess :: ExitCode -> Bool
105+isSuccess ExitSuccess = True
106+isSuccess (ExitFailure _) = False
107+
108+fileGlob :: String
109+fileGlob = unwords
110+ [ "\"src/**/*.purs\""
111+ , "\"src/**/*.js\""
112+ , "\"bower_components/purescript-*/**/*.purs\""
113+ , "\"bower_components/purescript-*/**/*.js\""
114+ ]
115+
116+-- Integration Testing API
117+
118+sendCommand :: Value -> IO String
119+sendCommand v = readCreateProcess
120+ ((shell "psc-ide-client") { std_out=CreatePipe
121+ , std_err=CreatePipe
122+ })
123+ (T.unpack (encodeT v))
124+
125+quitServer :: IO ()
126+quitServer = do
127+ let quitCommand = object ["command" .= ("quit" :: String)]
128+ _ <- try $ sendCommand quitCommand :: IO (Either SomeException String)
129+ return ()
130+
131+loadModuleWithDeps :: String -> IO String
132+loadModuleWithDeps m = sendCommand $ load [] [m]
133+
134+getFlexCompletions :: String -> IO [(String, String, String)]
135+getFlexCompletions q = parseCompletions <$> sendCommand (completion [] (Just (flexMatcher q)))
136+
137+getType :: String -> IO [(String, String, String)]
138+getType q = parseCompletions <$> sendCommand (typeC q [])
139+
140+addImport :: String -> FilePath -> FilePath -> IO String
141+addImport identifier fp outfp = sendCommand (addImportC identifier fp outfp)
142+
143+addImplicitImport :: String -> FilePath -> FilePath -> IO String
144+addImplicitImport mn fp outfp = sendCommand (addImplicitImportC mn fp outfp)
145+
146+-- Command Encoding
147+
148+commandWrapper :: String -> Value -> Value
149+commandWrapper c p = object ["command" .= c, "params" .= p]
150+
151+load :: [String] -> [String] -> Value
152+load ms ds = commandWrapper "load" (object ["modules" .= ms, "dependencies" .= ds])
153+
154+typeC :: String -> [Value] -> Value
155+typeC q filters = commandWrapper "type" (object ["search" .= q, "filters" .= filters])
156+
157+addImportC :: String -> FilePath -> FilePath -> Value
158+addImportC identifier = addImportW $
159+ object [ "importCommand" .= ("addImport" :: String)
160+ , "identifier" .= identifier
161+ ]
162+
163+addImplicitImportC :: String -> FilePath -> FilePath -> Value
164+addImplicitImportC mn = addImportW $
165+ object [ "importCommand" .= ("addImplicitImport" :: String)
166+ , "module" .= mn
167+ ]
168+
169+addImportW :: Value -> FilePath -> FilePath -> Value
170+addImportW importCommand fp outfp =
171+ commandWrapper "import" (object [ "file" .= fp
172+ , "outfile" .= outfp
173+ , "importCommand" .= importCommand
174+ ])
175+
176+
177+completion :: [Value] -> Maybe Value -> Value
178+completion filters matcher =
179+ let
180+ matcher' = case matcher of
181+ Nothing -> []
182+ Just m -> ["matcher" .= m]
183+ in
184+ commandWrapper "complete" (object $ "filters" .= filters : matcher')
185+
186+flexMatcher :: String -> Value
187+flexMatcher q = object [ "matcher" .= ("flex" :: String)
188+ , "params" .= object ["search" .= q]
189+ ]
190+
191+-- Result parsing
192+
193+unwrapResult :: Value -> Parser (Either String Value)
194+unwrapResult = withObject "result" $ \o -> do
195+ (rt :: String) <- o .: "resultType"
196+ case rt of
197+ "error" -> do
198+ res <- o .: "result"
199+ pure (Left res)
200+ "success" -> do
201+ res <- o .: "result"
202+ pure (Right res)
203+ _ -> fail "lol"
204+
205+withResult :: (Value -> Parser a) -> Value -> Parser (Either String a)
206+withResult p v = do
207+ r <- unwrapResult v
208+ case r of
209+ Left err -> pure (Left err)
210+ Right res -> Right <$> p res
211+
212+completionParser :: Value -> Parser [(String, String, String)]
213+completionParser = withArray "res" $ \cs ->
214+ mapM (withObject "completion" $ \o -> do
215+ ident <- o .: "identifier"
216+ module' <- o .: "module"
217+ ty <- o .: "type"
218+ pure (module', ident, ty)) (V.toList cs)
219+
220+valueFromString :: String -> Value
221+valueFromString = fromJust . decode . BSL.fromString
222+
223+resultIsSuccess :: String -> Bool
224+resultIsSuccess = isRight . join . parseEither unwrapResult . valueFromString
225+
226+parseCompletions :: String -> [(String, String, String)]
227+parseCompletions s = fromJust $ do
228+ cs <- parseMaybe (withResult completionParser) (valueFromString s)
229+ case cs of
230+ Left _ -> error "Failed to parse completions"
231+ Right cs' -> pure cs'
232+
233+parseTextResult :: String -> String
234+parseTextResult s = fromJust $ do
235+ r <- parseMaybe (withResult (withText "tr" pure)) (valueFromString s)
236+ case r of
237+ Left _ -> error "Failed to parse textResult"
238+ Right r' -> pure (T.unpack r')
tests/Language/PureScript/Ide/MatcherSpec.hs+32−7
@@ -2,24 +2,40 @@
22
33module Language.PureScript.Ide.MatcherSpec where
44
5−import Data.Text (Text)
5+import Data.Text (Text)
6+import Language.PureScript.Ide.Integration
67import Language.PureScript.Ide.Matcher
78import Language.PureScript.Ide.Types
9+import qualified Language.PureScript as P
810import Test.Hspec
911
10−completions :: [Completion]
12+value :: Text -> ExternDecl
13+value s = ValueDeclaration s P.TypeWildcard
14+
15+completions :: [Match]
1116completions = [
12− Completion ("", "firstResult", ""),
13− Completion ("", "secondResult", ""),
14− Completion ("", "fiult", "")
17+ Match "" $ value "firstResult",
18+ Match "" $ value "secondResult",
19+ Match "" $ value "fiult"
1520 ]
1621
17−mkResult :: [Int] -> [Completion]
22+mkResult :: [Int] -> [Match]
1823mkResult = map (completions !!)
1924
20−runFlex :: Text -> [Completion]
25+runFlex :: Text -> [Match]
2126runFlex s = runMatcher (flexMatcher s) completions
2227
28+setup :: IO ()
29+setup = do
30+ deleteOutputFolder
31+ _ <- compileTestProject
32+ _ <- startServer
33+ _ <- loadModuleWithDeps "Main"
34+ return ()
35+
36+teardown :: IO ()
37+teardown = quitServer
38+
2339spec :: Spec
2440spec = do
2541 describe "Flex Matcher" $ do
@@ -29,3 +45,12 @@ spec = do
2945 runFlex "firstResult" `shouldBe` mkResult [0]
3046 it "scores short matches higher and sorts accordingly" $
3147 runFlex "filt" `shouldBe` mkResult [2, 0]
48+
49+ beforeAll_ setup $ afterAll_ teardown $
50+ describe "Integration Tests: Flex Matcher" $ do
51+ it "doesn't match on an empty string" $ do
52+ cs <- getFlexCompletions ""
53+ cs `shouldBe` []
54+ it "matches on equality" $ do
55+ cs <- getFlexCompletions "const"
56+ cs `shouldBe` [("Main", "const", "forall a b. a -> b -> a")]
tests/Language/PureScript/Ide/ReexportsSpec.hs+5−4
@@ -6,14 +6,15 @@ import Data.List (sort)
66import qualified Data.Map as Map
77import Language.PureScript.Ide.Reexports
88import Language.PureScript.Ide.Types
9+import qualified Language.PureScript as P
910import Test.Hspec
1011
1112decl1 :: ExternDecl
12−decl1 = FunctionDecl "filter" "asdasd"
13+decl1 = ValueDeclaration "filter" P.TypeWildcard
1314decl2 :: ExternDecl
14−decl2 = DataDecl "Tree" "* -> *"
15+decl2 = ValueDeclaration "map" P.TypeWildcard
1516decl3 :: ExternDecl
16−decl3 = DataDecl "TreeAsd" "* -> *"
17+decl3 = ValueDeclaration "catMaybe" P.TypeWildcard
1718dep1 :: ExternDecl
1819dep1 = Dependency "Test.Foo" [] (Just "T")
1920dep2 :: ExternDecl
@@ -44,7 +45,7 @@ shouldBeEqualSorted :: Module -> Module -> Expectation
4445shouldBeEqualSorted (n1, d1) (n2, d2) = (n1, sort d1) `shouldBe` (n2, sort d2)
4546
4647spec :: Spec
47−spec = do
48+spec =
4849 describe "Reexports" $ do
4950 it "finds all reexports" $
5051 getReexports module1 `shouldBe` [Export "Module2", Export "Module3"]
tests/Language/PureScript/IdeSpec.hs+8−8
@@ -1,17 +1,17 @@
11{-# LANGUAGE OverloadedStrings #-}
2−{-# LANGUAGE PackageImports #-}
2+
33module Language.PureScript.IdeSpec where
44
55import Control.Concurrent.STM
66import Control.Monad.Reader
77import Data.List
8−import qualified Data.Map as Map
8+import qualified Data.Map as Map
99import Language.PureScript.Ide
1010import Language.PureScript.Ide.Types
1111import Test.Hspec
1212
1313testState :: PscIdeState
14−testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) (Map.empty)
14+testState = PscIdeState (Map.fromList [("Data.Array", []), ("Control.Monad.Eff", [])]) Map.empty
1515
1616defaultConfig :: Configuration
1717defaultConfig =
@@ -22,13 +22,13 @@ defaultConfig =
2222 }
2323
2424spec :: SpecWith ()
25−spec = do
26− describe "list" $ do
25+spec =
26+ describe "list" $
2727 describe "loadedModules" $ do
2828 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 []
29+ st <- newTVarIO emptyPscIdeState
30+ result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
31+ result `shouldBe` ModuleList []
3232 it "returns the list of loaded modules" $ do
3333 st <- newTVarIO testState
3434 ModuleList result <- runReaderT printModules (PscIdeEnvironment st defaultConfig)
tests/support/pscide/.gitignorenew file+7
@@ -0,0 +1,7 @@
1+/bower_components/
2+/node_modules/
3+/.pulp-cache/
4+/output/
5+/.psci*
6+/src/.webpack.js
7+/src/*.tmp
tests/support/pscide/src/ImportsSpec.pursnew file+5
@@ -0,0 +1,5 @@
1+module ImportsSpec where
2+
3+import Main (id)
4+
5+myId = id
tests/support/pscide/src/ImportsSpec1.pursnew file+32
@@ -0,0 +1,32 @@
1+module ImportsSpec1
2+ ( exportedFunction
3+ , MyType
4+ , MyParamType
5+ , MyNewtype(..)
6+ , MyMaybe(..)
7+ , SpecialCase
8+ , X(..)
9+ , class ATypeClass
10+ , typeClassFun
11+ , OnlyTypeExported
12+ )
13+ where
14+
15+exportedFunction ∷ ∀ a. a → a
16+exportedFunction x = x
17+
18+type MyType = String
19+
20+type MyParamType a = Array a
21+
22+newtype MyNewtype = MyNewtype String
23+
24+data MyMaybe a = MyJust a | MyNothing
25+
26+data SpecialCase
27+data X = SpecialCase
28+
29+newtype OnlyTypeExported = OnlyTypeExported String
30+
31+class ATypeClass a where
32+ typeClassFun ∷ a -> a
tests/support/pscide/src/Main.pursnew file+7
@@ -0,0 +1,7 @@
1+module Main where
2+
3+id :: forall a. a -> a
4+id x = x
5+
6+const :: forall a b. a -> b -> a
7+const x _ = x