Skip to content

Commit d9687d2

Browse files
committed
Don't output module snippets when present
1 parent 0779306 commit d9687d2

5 files changed

Lines changed: 97 additions & 28 deletions

File tree

ghcide-test/exe/CompletionTests.hs

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -625,8 +625,6 @@ contextCompletionTests =
625625
(Position 4 8)
626626
[("Xxxtype", CompletionItemKind_Struct, "Xxxtype", False, True, Nothing)]
627627

628-
-- where-clause / local binding context tests
629-
630628
, completionTest
631629
"type sig in where-clause gives type completions"
632630
[ "{-# OPTIONS_GHC -Wunused-binds #-}"
@@ -726,6 +724,25 @@ contextCompletionTests =
726724
]
727725
(Position 5 19) -- after "Xxx" in " let helper :: Xxx"
728726
[("Xxxtype", CompletionItemKind_Struct, "Xxxtype", False, True, Nothing)]
727+
728+
, testSessionSingleFile "module header snippet shown when no module declaration" "A.hs"
729+
[ "mod"
730+
] $ do
731+
doc <- openDoc "A.hs" "haskell"
732+
_ <- waitForDiagnostics
733+
compls <- getCompletions doc (Position 0 3)
734+
let moduleSnippets = filterSnippetsLabel "module" compls
735+
liftIO $ length moduleSnippets @?= 1
736+
737+
, testSessionSingleFile "module header snippet not shown when module declaration exists" "A.hs"
738+
[ "module A where"
739+
, "mod"
740+
] $ do
741+
doc <- openDoc "A.hs" "haskell"
742+
_ <- waitForDiagnostics
743+
compls <- getCompletions doc (Position 1 3)
744+
let moduleSnippets = filterSnippetsLabel "module" compls
745+
liftIO $ moduleSnippets @?= []
729746
]
730747
where
731748
filterSnippetsLabel l snippets =

ghcide/src/Development/IDE/Plugin/Completions.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,10 @@ getCompletionsLSP recorder ide plId
216216
let clientCaps = clientCapabilities $ shakeExtras ide
217217
plugins = idePlugins $ shakeExtras ide
218218
context = deduceContext ctxTree (cursorPos pfix)
219+
hasModuleHeader = maybe False (contextHasModuleHeader . fst) ctxTree
219220
config <- liftIO $ runAction "" ide $ getCompletionsConfig plId
220221

221-
let allCompletions = getCompletions plugins ideOpts cci' context astres bindMap pfix clientCaps config moduleExports uri
222+
let allCompletions = getCompletions plugins ideOpts cci' context hasModuleHeader astres bindMap pfix clientCaps config moduleExports uri
222223
logWith recorder Debug $ LogDetectedContext context
223224
pure $ InL (orderedCompletions allCompletions)
224225
_ -> return (InL [])

ghcide/src/Development/IDE/Plugin/Completions/Context.hs

Lines changed: 65 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ module Development.IDE.Plugin.Completions.Context
88
, ContextGroup (..)
99
, ContextMap
1010
, GetContextMap (..)
11+
, contextHasModuleHeader
1112
, getContext
1213
, getContextMap
1314
) where
1415

1516
import Control.DeepSeq (NFData (..), rwhnf)
16-
import Data.Generics (extQ, mkQ)
17-
import Data.Generics.Schemes (everythingBut)
17+
import Data.Generics (Data (..), GenericQ,
18+
extQ, mkQ)
1819
import Data.Hashable (Hashable)
19-
import Data.Maybe (mapMaybe)
20+
import Data.List.Extra (nubOrd)
21+
import Data.Maybe (fromJust, isJust,
22+
mapMaybe)
2023
import qualified Data.Text as T
2124
import Development.IDE
2225
import Development.IDE.Core.PositionMapping
@@ -73,8 +76,8 @@ data ContextChunk = Chunk
7376
}
7477

7578
-- | Build lazy 'ContextChunk' by processing @n@ source items at a time.
76-
groupedChunks :: Int -> ContextGroup -> (a -> Maybe Range) -> (a -> Range -> ContextResult) -> [a] -> ContextMap
77-
groupedChunks n group getPos locate xs = ContextMap $ go xs
79+
groupedChunks :: Int -> ContextGroup -> (a -> Maybe Range) -> (a -> Range -> ContextResult) -> [a] -> [ContextChunk]
80+
groupedChunks n group getPos locate xs = go xs
7881
where
7982
go [] = []
8083
go xs =
@@ -89,9 +92,15 @@ groupedChunks n group getPos locate xs = ContextMap $ go xs
8992
, context
9093
} : go rest
9194

95+
-- | Build lazy 'ContextChunk' by processing @n@ source items at a time.
96+
singletonChunk :: ContextGroup -> (a -> Maybe Range) -> (a -> Range -> ContextResult) -> a -> ContextChunk
97+
singletonChunk group getPos locate inp = Chunk s e group (locate inp)
98+
where
99+
Range s e = fromJust $ getPos inp
100+
92101
-- | Used during context finding, combines into the tightest interval.
93102
-- As an intuition, the primary interface is through
94-
-- @Monoid (Position -> ContextResult)@.
103+
-- @Monoid (Range -> ContextResult)@.
95104
data ContextResult = NoContext | ContextResult Range Context
96105
instance Monoid ContextResult where mempty = NoContext
97106
instance Semigroup ContextResult where (<>) = tighten
@@ -102,8 +111,14 @@ tighten a NoContext = a
102111
tighten ar@(ContextResult a _) br@(ContextResult b _) =
103112
if a `dominates` b then br else ar
104113

105-
newtype ContextMap = ContextMap [ContextChunk]
106-
deriving newtype (Monoid, Semigroup)
114+
-- | A context map, built from a parsed module. Stores whether the module
115+
-- already has a @module ... where@ header, so that the header snippet can
116+
-- be suppressed for files that already declare a module.
117+
data ContextMap = ContextMap !Bool [ContextChunk]
118+
instance Semigroup ContextMap where
119+
ContextMap h1 c1 <> ContextMap h2 c2 = ContextMap (h1 || h2) (c1 <> c2)
120+
instance Monoid ContextMap where
121+
mempty = ContextMap False []
107122
instance Show ContextMap where show _ = "<context map>"
108123
instance NFData ContextMap where rnf = rwhnf
109124

@@ -116,10 +131,14 @@ instance NFData ContextMap where rnf = rwhnf
116131
-- as a Shake rule.
117132
getContextMap :: ParsedModule -> ContextMap
118133
getContextMap pm =
119-
groupedChunks 10 ImportGroup rangeOf getImportContext hsmodImports
120-
<> groupedChunks 5 DeclarationGroup rangeOf getDeclContext hsmodDecls
134+
ContextMap (isJust hsmodName) $
135+
-- These denote the size of the "jumps" of the cursor when traversing the AST.
136+
-- Reduces the amount of data we have to look at with syb.
137+
singletonChunk HeaderGroup rangeOf getHeaderContext hsmodName
138+
: groupedChunks 10 ImportGroup rangeOf getImportContext hsmodImports
139+
<> groupedChunks 2 DeclarationGroup rangeOf getDeclContext hsmodDecls
121140
where
122-
HsModule {hsmodImports, hsmodDecls} =
141+
HsModule {hsmodName, hsmodImports, hsmodDecls} =
123142
unLoc (pm_parsed_source pm)
124143

125144
#if MIN_VERSION_ghc(9,9,0)
@@ -130,47 +149,59 @@ rangeOf :: GenLocated (SrcSpanAnn' a) e -> Maybe Range
130149
rangeOf = srcSpanToRange . getLocA
131150
#endif
132151

152+
getHeaderContext :: Data a => a -> Range -> ContextResult
153+
getHeaderContext decl query =
154+
gather
155+
(<>)
156+
((mempty, False) `mkQ` modNameQ query)
157+
decl
158+
133159
getImportContext :: LImportDecl GhcPs -> Range -> ContextResult
134160
getImportContext imports query =
135-
everythingBut
161+
gather
136162
(<>)
137163
((mempty, False) `mkQ` importQ query)
138164
imports
139165

140166
getDeclContext :: LHsDecl GhcPs -> Range -> ContextResult
141167
getDeclContext declarations query =
142-
everythingBut
168+
gather
143169
(<>)
144170
((mempty, False) `mkQ` sigQ query `extQ` bindQ query `extQ` declQ query)
145171
declarations
146172

147173
-- * Querying
148174

175+
-- | Returns 'True' when the parsed module already has a @module ... where@
176+
-- declaration. Used downstream to suppress the module header snippet.
177+
contextHasModuleHeader :: ContextMap -> Bool
178+
contextHasModuleHeader (ContextMap h _) = h
179+
149180
-- | Look up the completion context at a given position.
150181
-- Returns the innermost (most specific) context that contains the position.
151182
--
152183
-- Only the 'ContextChunks' up to and including the chunk containing the
153184
-- query position are forced; later chunks remain as unevaluated thunks.
154185
getContext :: ContextMap -> PositionResult Position -> Context
155-
getContext (ContextMap chunks) query =
156-
case searchChunks True chunks of
157-
(groups, NoContext) -> TopContext groups
186+
getContext (ContextMap _ chunks) query =
187+
case searchChunks HeaderGroup chunks of
188+
(groups, NoContext) -> TopContext $ nubOrd groups
158189
(_, ContextResult _ found) -> found
159190
where
160191
(qLo, qHi) = case query of
161192
PositionExact p -> (p, p)
162193
PositionRange l u -> (l, u)
163194

164-
searchChunks :: Bool -> [ContextChunk] -> ([ContextGroup], ContextResult)
195+
searchChunks :: ContextGroup -> [ContextChunk] -> ([ContextGroup], ContextResult)
165196
searchChunks _ [] = ([], mempty)
166-
searchChunks firstChunk (Chunk cLo cHi group contextOf : rest)
197+
searchChunks lastChunk (Chunk cLo cHi group contextOf : rest)
167198
| -- query is past this chunk (line-only comparison so cursors
168199
-- past the last column on the final line still match)
169-
_line qLo > _line cHi = searchChunks False rest
200+
_line qLo > _line cHi = searchChunks group rest
170201
| -- query is before this chunk
171-
qHi < cLo = (if firstChunk then [HeaderGroup] else [], mempty)
202+
qHi < cLo = ([lastChunk, group], mempty)
172203
-- this chunk is relevant, emit the group and all relevant intervals
173-
| otherwise = ([group], contextOf (Range qLo qHi)) <> searchChunks False rest
204+
| otherwise = ([group], contextOf (Range qLo qHi)) <> searchChunks group rest
174205

175206
-- * SYB queries types
176207

@@ -200,6 +231,9 @@ declQ query (L (locA -> ss) decl) = case srcSpanToRange ss of
200231
SpliceD {} -> (ContextResult range (TopContext []), True)
201232
_ -> (ContextResult range DefaultContext, True) -- DefD, WarningD, AnnD, RuleD, DocD, KindSigD
202233

234+
modNameQ :: Range -> XRec GhcPs ModuleName -> (ContextResult, Bool)
235+
modNameQ = contextual (TopContext [HeaderGroup]) True
236+
203237
sigQ :: Range -> LSig GhcPs -> (ContextResult, Bool)
204238
sigQ = contextual TypeContext True
205239

@@ -247,3 +281,13 @@ instance Pretty ContextGroup where
247281
HeaderGroup -> "header"
248282
ImportGroup -> "imports"
249283
DeclarationGroup -> "declarations"
284+
285+
-- | Variation of @Data.Generics.Schemes.everythingBut@, but uses foldr.
286+
gather :: forall r. (r -> r -> r) -> GenericQ (r, Bool) -> GenericQ r
287+
gather k f = go
288+
where
289+
go :: GenericQ r
290+
go x = let (v, stop) = f x
291+
in if stop
292+
then v
293+
else foldr k v (gmapQ go x)

ghcide/src/Development/IDE/Plugin/Completions/Logic.hs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,7 @@ getCompletions
453453
-> IdeOptions
454454
-> CachedCompletions
455455
-> Context
456+
-> Bool
456457
-> Maybe (HieAstResult, PositionMapping)
457458
-> (Bindings, PositionMapping)
458459
-> PosPrefixInfo
@@ -466,6 +467,7 @@ getCompletions
466467
ideOpts
467468
CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
468469
context
470+
hasModuleHeader
469471
maybe_ast_res
470472
(localBindings, bmapping)
471473
prefixInfo@(PosPrefixInfo { fullLine, prefixScope, prefixText })
@@ -636,7 +638,7 @@ getCompletions
636638
filtTopContextCompls :: [Context.ContextGroup] -> [Scored CompletionItem]
637639
filtTopContextCompls groups
638640
| T.null prefixScope
639-
= Fuzzy.filter chunkSize maxC fullPrefix (getContextSnippets groups) (view L.label)
641+
= Fuzzy.filter chunkSize maxC fullPrefix (getContextSnippets hasModuleHeader groups) (view L.label)
640642
| otherwise = []
641643

642644
-- We use this ordering to alphabetically sort suggestions while respecting

ghcide/src/Development/IDE/Plugin/Completions/Snippet.hs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ data SnippetCompletion = SnippetCompletion
1818
, snippetContents :: {-# UNPACK #-} !Text
1919
}
2020

21-
getContextSnippets :: [ContextGroup] -> [CompletionItem]
22-
getContextSnippets [] = concatMap (fmap mkSnippetCompletion . snd) topContextSnippets
23-
getContextSnippets groups = concatMap (fmap mkSnippetCompletion . concat . maybeToList . (`lookup` topContextSnippets)) groups
21+
getContextSnippets :: Bool -> [ContextGroup] -> [CompletionItem]
22+
getContextSnippets hasModuleHeader groups =
23+
let tbl = if hasModuleHeader
24+
then filter ((/= HeaderGroup) . fst) topContextSnippets
25+
else topContextSnippets
26+
in fmap mkSnippetCompletion $ case groups of
27+
[] -> concatMap snd tbl
28+
_ -> concatMap (concat . maybeToList . (`lookup` tbl)) groups
2429

2530
topContextSnippets :: [(ContextGroup, [SnippetCompletion])]
2631
topContextSnippets =

0 commit comments

Comments
 (0)