Skip to content

Commit 2bd101d

Browse files
authored
[purs ide] Groups reexports in completions (purescript#2907)
* [purs ide] Groups reexports in completions Adds an option to control how reexport should be grouped in the completions. * adds exportedFrom field to completions * Simplify grouping If the groupReexports flag is set, we just treat every export as a reexport (Even from the origin module). This will require the editors to do a little more work, but makes the behaviours consistent and predictable. * yak shave * documents the new behaviour * update documentation
1 parent 543a54f commit 2bd101d

File tree

8 files changed

+149
-75
lines changed

8 files changed

+149
-75
lines changed

psc-ide/PROTOCOL.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ The `complete` command looks up possible completions/corrections.
8181
"matcher": {..},
8282
"currentModule": "Main",
8383
"options": {
84-
"maxResults": 50
84+
"maxResults": 50,
85+
"groupReexports": true
8586
}
8687
}
8788
}
@@ -107,7 +108,8 @@ couldn't be extracted from a source file.
107108
"start": [1, 3],
108109
"end": [3, 1]
109110
},
110-
"documentation": "A filtering function"
111+
"documentation": "A filtering function",
112+
"exportedFrom": ["Data.Array"]
111113
}
112114
]
113115
```
@@ -614,6 +616,13 @@ Completion options allow to configure the number of returned completion results.
614616
If specified limits the number of completion results, otherwise return all
615617
results.
616618

619+
- groupReexports :: Maybe Boolean (defaults to False)
620+
621+
If set to True, groups all reexports of an identifier under the module it
622+
originated from (the original export is also treated as a "reexport"). These
623+
reexports then populate the `exportedFrom` field in their completion results and
624+
the `module` field contains the originating module.
625+
617626
### Error
618627

619628
Errors at this point are merely Error strings. Newlines are escaped like `\n`

src/Language/PureScript/Ide.hs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ handleCommand c = case c of
8383
case rs of
8484
Right rs' -> answerRequest outfp rs'
8585
Left question ->
86-
pure (CompletionResult (map (completionFromMatch . map withEmptyAnn) question))
86+
pure (CompletionResult (map (completionFromMatch . simpleExport . map withEmptyAnn) question))
8787
Rebuild file ->
8888
rebuildFileAsync file
8989
RebuildSync file ->
@@ -104,13 +104,13 @@ findCompletions
104104
-> m Success
105105
findCompletions filters matcher currentModule complOptions = do
106106
modules <- getAllModules currentModule
107-
pure . CompletionResult . map completionFromMatch . getCompletions filters matcher complOptions $ modules
107+
pure (CompletionResult (getCompletions filters matcher complOptions modules))
108108

109109
findType :: Ide m =>
110110
Text -> [Filter] -> Maybe P.ModuleName -> m Success
111111
findType search filters currentModule = do
112112
modules <- getAllModules currentModule
113-
pure . CompletionResult . map completionFromMatch . getExactMatches search filters $ modules
113+
pure (CompletionResult (getExactCompletions search filters modules))
114114

115115
findPursuitCompletions :: MonadIO m =>
116116
PursuitQuery -> m Success
Lines changed: 96 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,26 @@
11
module Language.PureScript.Ide.Completion
22
( getCompletions
33
, getExactMatches
4+
, getExactCompletions
5+
, simpleExport
6+
, completionFromMatch
47
, CompletionOptions(..)
58
, defaultCompletionOptions
69
, applyCompletionOptions
710
) where
811

912
import Protolude
1013

14+
import Control.Lens hiding ((&), op)
1115
import Data.Aeson
16+
import qualified Data.Map as Map
17+
import qualified Data.Text as T
18+
import qualified Language.PureScript as P
19+
import Language.PureScript.Ide.Error (prettyPrintTypeSingleLine)
1220
import Language.PureScript.Ide.Filter
1321
import Language.PureScript.Ide.Matcher
1422
import Language.PureScript.Ide.Types
15-
import qualified Language.PureScript as P
23+
import Language.PureScript.Ide.Util
1624

1725
type Module = (P.ModuleName, [IdeDeclarationAnn])
1826

@@ -23,37 +31,111 @@ getCompletions
2331
-> Matcher IdeDeclarationAnn
2432
-> CompletionOptions
2533
-> [Module]
26-
-> [Match IdeDeclarationAnn]
34+
-> [Completion]
2735
getCompletions filters matcher options modules =
2836
modules
2937
& applyFilters filters
30-
& completionsFromModules
38+
& matchesFromModules
3139
& runMatcher matcher
3240
& applyCompletionOptions options
41+
<&> completionFromMatch
3342

3443
getExactMatches :: Text -> [Filter] -> [Module] -> [Match IdeDeclarationAnn]
3544
getExactMatches search filters modules =
36-
completionsFromModules (applyFilters (equalityFilter search : filters) modules)
45+
modules
46+
& applyFilters (equalityFilter search : filters)
47+
& matchesFromModules
3748

38-
completionsFromModules :: [Module] -> [Match IdeDeclarationAnn]
39-
completionsFromModules = foldMap completionFromModule
49+
getExactCompletions :: Text -> [Filter] -> [Module] -> [Completion]
50+
getExactCompletions search filters modules =
51+
modules
52+
& getExactMatches search filters
53+
<&> simpleExport
54+
<&> completionFromMatch
55+
56+
matchesFromModules :: [Module] -> [Match IdeDeclarationAnn]
57+
matchesFromModules = foldMap completionFromModule
4058
where
4159
completionFromModule (moduleName, decls) =
4260
map (\x -> Match (moduleName, x)) decls
4361

4462
data CompletionOptions = CompletionOptions
4563
{ coMaxResults :: Maybe Int
64+
, coGroupReexports :: Bool
4665
}
4766

48-
defaultCompletionOptions :: CompletionOptions
49-
defaultCompletionOptions = CompletionOptions { coMaxResults = Nothing }
50-
51-
applyCompletionOptions :: CompletionOptions -> [a] -> [a]
52-
applyCompletionOptions co =
53-
maybe identity take (coMaxResults co)
54-
5567
instance FromJSON CompletionOptions where
5668
parseJSON = withObject "CompletionOptions" $ \o -> do
5769
maxResults <- o .:? "maxResults"
58-
pure (CompletionOptions { coMaxResults = maxResults })
70+
groupReexports <- o .:? "groupReexports" .!= False
71+
pure (CompletionOptions { coMaxResults = maxResults
72+
, coGroupReexports = groupReexports
73+
})
74+
75+
defaultCompletionOptions :: CompletionOptions
76+
defaultCompletionOptions = CompletionOptions { coMaxResults = Nothing, coGroupReexports = False }
77+
78+
applyCompletionOptions :: CompletionOptions -> [Match IdeDeclarationAnn] -> [(Match IdeDeclarationAnn, [P.ModuleName])]
79+
applyCompletionOptions co decls =
80+
maybe identity take (coMaxResults co) decls
81+
& if coGroupReexports co
82+
then groupCompletionReexports
83+
else map simpleExport
84+
85+
simpleExport :: Match a -> (Match a, [P.ModuleName])
86+
simpleExport match@(Match (moduleName, _)) = (match, [moduleName])
87+
88+
groupCompletionReexports :: [Match IdeDeclarationAnn] -> [(Match IdeDeclarationAnn, [P.ModuleName])]
89+
groupCompletionReexports initial =
90+
Map.elems (foldr go Map.empty initial)
91+
where
92+
go (Match (moduleName, d@(IdeDeclarationAnn ann decl))) =
93+
let
94+
origin = fromMaybe moduleName (ann^.annExportedFrom)
95+
in
96+
Map.alter
97+
(insertDeclaration moduleName origin d)
98+
(Namespaced (namespaceForDeclaration decl)
99+
(P.runModuleName origin <> "." <> identifierFromIdeDeclaration decl))
100+
insertDeclaration moduleName origin d old = case old of
101+
Nothing -> Just ( Match (origin, d & idaAnnotation.annExportedFrom .~ Nothing)
102+
, [moduleName]
103+
)
104+
Just x -> Just (second (moduleName :) x)
105+
106+
data Namespaced a = Namespaced IdeNamespace a
107+
deriving (Show, Eq, Ord)
108+
109+
completionFromMatch :: (Match IdeDeclarationAnn, [P.ModuleName]) -> Completion
110+
completionFromMatch (Match (m, IdeDeclarationAnn ann decl), mns) =
111+
Completion {..}
112+
where
113+
(complIdentifier, complExpandedType) = case decl of
114+
IdeDeclValue v -> (v ^. ideValueIdent . identT, v ^. ideValueType & prettyPrintTypeSingleLine)
115+
IdeDeclType t -> (t ^. ideTypeName . properNameT, t ^. ideTypeKind & P.prettyPrintKind)
116+
IdeDeclTypeSynonym s -> (s ^. ideSynonymName . properNameT, s ^. ideSynonymType & prettyPrintTypeSingleLine)
117+
IdeDeclDataConstructor d -> (d ^. ideDtorName . properNameT, d ^. ideDtorType & prettyPrintTypeSingleLine)
118+
IdeDeclTypeClass d -> (d ^. ideTCName . properNameT, d ^. ideTCKind & P.prettyPrintKind)
119+
IdeDeclValueOperator (IdeValueOperator op ref precedence associativity typeP) ->
120+
(P.runOpName op, maybe (showFixity precedence associativity (valueOperatorAliasT ref) op) prettyPrintTypeSingleLine typeP)
121+
IdeDeclTypeOperator (IdeTypeOperator op ref precedence associativity kind) ->
122+
(P.runOpName op, maybe (showFixity precedence associativity (typeOperatorAliasT ref) op) P.prettyPrintKind kind)
123+
IdeDeclKind k -> (P.runProperName k, "kind")
124+
125+
complExportedFrom = mns
126+
127+
complModule = P.runModuleName m
128+
129+
complType = maybe complExpandedType prettyPrintTypeSingleLine (_annTypeAnnotation ann)
130+
131+
complLocation = _annLocation ann
132+
133+
complDocumentation = Nothing
134+
135+
showFixity p a r o =
136+
let asso = case a of
137+
P.Infix -> "infix"
138+
P.Infixl -> "infixl"
139+
P.Infixr -> "infixr"
140+
in T.unwords [asso, show p, r, "as", P.runOpName o]
59141

src/Language/PureScript/Ide/Error.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,9 @@ encodeRebuildErrors = toJSON . map encodeRebuildError . P.runMultipleErrors
6666
insertTSCompletions _ _ _ v = v
6767

6868
identCompletion (P.Qualified mn i, ty) =
69-
Completion (maybe "" P.runModuleName mn) i (prettyPrintTypeSingleLine ty) (prettyPrintTypeSingleLine ty) Nothing Nothing
69+
Completion (maybe "" P.runModuleName mn) i (prettyPrintTypeSingleLine ty) (prettyPrintTypeSingleLine ty) Nothing Nothing (maybe [] (\x -> [x]) mn)
7070
fieldCompletion (label, ty) =
71-
Completion "" ("_." <> P.prettyPrintLabel label) (prettyPrintTypeSingleLine ty) (prettyPrintTypeSingleLine ty) Nothing Nothing
71+
Completion "" ("_." <> P.prettyPrintLabel label) (prettyPrintTypeSingleLine ty) (prettyPrintTypeSingleLine ty) Nothing Nothing []
7272

7373
textError :: IdeError -> Text
7474
textError (GeneralError msg) = msg

src/Language/PureScript/Ide/Filter.hs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,7 @@ namespaceFilter namespaces =
4646
mkFilter (filterModuleDecls filterNamespaces)
4747
where
4848
filterNamespaces :: IdeDeclaration -> Bool
49-
filterNamespaces decl = elem (namespace decl) namespaces
50-
namespace :: IdeDeclaration -> IdeNamespace
51-
namespace (IdeDeclValue _) = IdeNSValue
52-
namespace (IdeDeclType _) = IdeNSType
53-
namespace (IdeDeclTypeSynonym _) = IdeNSType
54-
namespace (IdeDeclDataConstructor _) = IdeNSValue
55-
namespace (IdeDeclTypeClass _) = IdeNSType
56-
namespace (IdeDeclValueOperator _) = IdeNSValue
57-
namespace (IdeDeclTypeOperator _) = IdeNSType
58-
namespace (IdeDeclKind _) = IdeNSKind
49+
filterNamespaces decl = elem (namespaceForDeclaration decl) namespaces
5950

6051
-- | Only keeps the given Modules
6152
moduleFilter :: [P.ModuleName] -> Filter

src/Language/PureScript/Ide/Types.hs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ import Protolude
2222
import Control.Concurrent.STM
2323
import Control.Lens.TH
2424
import Data.Aeson
25-
import qualified Data.Map.Lazy as M
26-
import qualified Language.PureScript as P
27-
import qualified Language.PureScript.Errors.JSON as P
25+
import qualified Data.Map.Lazy as M
26+
import qualified Language.PureScript as P
27+
import qualified Language.PureScript.Errors.JSON as P
2828

2929
type ModuleIdent = Text
3030
type ModuleMap a = Map P.ModuleName a
@@ -193,6 +193,7 @@ data Completion = Completion
193193
, complExpandedType :: Text
194194
, complLocation :: Maybe P.SourceSpan
195195
, complDocumentation :: Maybe Text
196+
, complExportedFrom :: [P.ModuleName]
196197
} deriving (Show, Eq, Ord)
197198

198199
instance ToJSON Completion where
@@ -203,6 +204,7 @@ instance ToJSON Completion where
203204
, "expandedType" .= complExpandedType
204205
, "definedAt" .= complLocation
205206
, "documentation" .= complDocumentation
207+
, "exportedFrom" .= complExportedFrom
206208
]
207209

208210
identifierFromDeclarationRef :: P.DeclarationRef -> Text

src/Language/PureScript/Ide/Util.hs

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ module Language.PureScript.Ide.Util
1717
, unwrapMatch
1818
, unwrapPositioned
1919
, unwrapPositionedRef
20-
, completionFromMatch
20+
, namespaceForDeclaration
2121
, encodeT
2222
, decodeT
2323
, discardAnn
@@ -40,7 +40,7 @@ import qualified Data.Text as T
4040
import qualified Data.Text.Lazy as TL
4141
import Data.Text.Lazy.Encoding as TLE
4242
import qualified Language.PureScript as P
43-
import Language.PureScript.Ide.Error (prettyPrintTypeSingleLine, IdeError(..))
43+
import Language.PureScript.Ide.Error (IdeError(..))
4444
import Language.PureScript.Ide.Logging
4545
import Language.PureScript.Ide.Types
4646
import System.IO.UTF8 (readUTF8FileT)
@@ -56,6 +56,17 @@ identifierFromIdeDeclaration d = case d of
5656
IdeDeclTypeOperator op -> op ^. ideTypeOpName & P.runOpName
5757
IdeDeclKind name -> P.runProperName name
5858

59+
namespaceForDeclaration :: IdeDeclaration -> IdeNamespace
60+
namespaceForDeclaration d = case d of
61+
IdeDeclValue _ -> IdeNSValue
62+
IdeDeclType _ -> IdeNSType
63+
IdeDeclTypeSynonym _ -> IdeNSType
64+
IdeDeclDataConstructor _ -> IdeNSValue
65+
IdeDeclTypeClass _ -> IdeNSType
66+
IdeDeclValueOperator _ -> IdeNSValue
67+
IdeDeclTypeOperator _ -> IdeNSType
68+
IdeDeclKind _ -> IdeNSKind
69+
5970
discardAnn :: IdeDeclarationAnn -> IdeDeclaration
6071
discardAnn (IdeDeclarationAnn _ d) = d
6172

@@ -65,37 +76,6 @@ withEmptyAnn = IdeDeclarationAnn emptyAnn
6576
unwrapMatch :: Match a -> a
6677
unwrapMatch (Match (_, ed)) = ed
6778

68-
completionFromMatch :: Match IdeDeclarationAnn -> Completion
69-
completionFromMatch (Match (m, IdeDeclarationAnn ann decl)) =
70-
Completion {..}
71-
where
72-
(complIdentifier, complExpandedType) = case decl of
73-
IdeDeclValue v -> (v ^. ideValueIdent . identT, v ^. ideValueType & prettyPrintTypeSingleLine)
74-
IdeDeclType t -> (t ^. ideTypeName . properNameT, t ^. ideTypeKind & P.prettyPrintKind)
75-
IdeDeclTypeSynonym s -> (s ^. ideSynonymName . properNameT, s ^. ideSynonymType & prettyPrintTypeSingleLine)
76-
IdeDeclDataConstructor d -> (d ^. ideDtorName . properNameT, d ^. ideDtorType & prettyPrintTypeSingleLine)
77-
IdeDeclTypeClass d -> (d ^. ideTCName . properNameT, d ^. ideTCKind & P.prettyPrintKind)
78-
IdeDeclValueOperator (IdeValueOperator op ref precedence associativity typeP) ->
79-
(P.runOpName op, maybe (showFixity precedence associativity (valueOperatorAliasT ref) op) prettyPrintTypeSingleLine typeP)
80-
IdeDeclTypeOperator (IdeTypeOperator op ref precedence associativity kind) ->
81-
(P.runOpName op, maybe (showFixity precedence associativity (typeOperatorAliasT ref) op) P.prettyPrintKind kind)
82-
IdeDeclKind k -> (P.runProperName k, "kind")
83-
84-
complModule = P.runModuleName m
85-
86-
complType = maybe complExpandedType prettyPrintTypeSingleLine (_annTypeAnnotation ann)
87-
88-
complLocation = _annLocation ann
89-
90-
complDocumentation = Nothing
91-
92-
showFixity p a r o =
93-
let asso = case a of
94-
P.Infix -> "infix"
95-
P.Infixl -> "infixl"
96-
P.Infixr -> "infixr"
97-
in T.unwords [asso, show p, r, "as", P.runOpName o]
98-
9979
valueOperatorAliasT
10080
:: P.Qualified (Either P.Ident (P.ProperName 'P.ConstructorName)) -> Text
10181
valueOperatorAliasT i =

tests/Language/PureScript/Ide/CompletionSpec.hs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,31 @@ module Language.PureScript.Ide.CompletionSpec where
44

55
import Protolude
66

7+
import Language.PureScript as P
78
import Language.PureScript.Ide.Completion
89
import Language.PureScript.Ide.Test
910
import Language.PureScript.Ide.Types
1011
import Test.Hspec
1112

12-
matches :: [Match IdeDeclarationAnn]
13-
matches = map (\d -> Match (mn "Main", d)) [ ideKind "Kind", ideType "Type" Nothing ]
13+
reexportMatches :: [Match IdeDeclarationAnn]
14+
reexportMatches =
15+
map (\d -> Match (mn "A", d)) moduleA
16+
++ map (\d -> Match (mn "B", d)) moduleB
17+
where
18+
moduleA = [ideKind "Kind"]
19+
moduleB = [ideKind "Kind" `annExp` "A"]
20+
21+
matches :: [(Match IdeDeclarationAnn, [P.ModuleName])]
22+
matches = map (\d -> (Match (mn "Main", d), [mn "Main"])) [ ideKind "Kind", ideType "Type" Nothing ]
1423

1524
spec :: Spec
1625
spec = describe "Applying completion options" $ do
1726
it "keeps all matches if maxResults is not specified" $ do
18-
applyCompletionOptions (defaultCompletionOptions { coMaxResults = Nothing }) matches
19-
`shouldBe`
20-
matches
27+
applyCompletionOptions (defaultCompletionOptions { coMaxResults = Nothing })
28+
(map fst matches) `shouldMatchList` matches
2129
it "keeps only the specified amount of maxResults" $ do
22-
applyCompletionOptions (defaultCompletionOptions { coMaxResults = Just 1 }) matches
23-
`shouldBe`
24-
take 1 matches
30+
applyCompletionOptions (defaultCompletionOptions { coMaxResults = Just 1 })
31+
(map fst matches) `shouldMatchList` take 1 matches
32+
it "groups reexports for a single identifier" $ do
33+
applyCompletionOptions (defaultCompletionOptions { coGroupReexports = True })
34+
reexportMatches `shouldBe` [(Match (mn "A", ideKind "Kind"), [mn "A", mn "B"])]

0 commit comments

Comments
 (0)