@@ -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
1516import 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 )
1819import Data.Hashable (Hashable )
19- import Data.Maybe (mapMaybe )
20+ import Data.List.Extra (nubOrd )
21+ import Data.Maybe (fromJust , isJust ,
22+ mapMaybe )
2023import qualified Data.Text as T
2124import Development.IDE
2225import 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)@.
95104data ContextResult = NoContext | ContextResult Range Context
96105instance Monoid ContextResult where mempty = NoContext
97106instance Semigroup ContextResult where (<>) = tighten
@@ -102,8 +111,14 @@ tighten a NoContext = a
102111tighten 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 []
107122instance Show ContextMap where show _ = " <context map>"
108123instance NFData ContextMap where rnf = rwhnf
109124
@@ -116,10 +131,14 @@ instance NFData ContextMap where rnf = rwhnf
116131-- as a Shake rule.
117132getContextMap :: ParsedModule -> ContextMap
118133getContextMap 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
130149rangeOf = 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+
133159getImportContext :: LImportDecl GhcPs -> Range -> ContextResult
134160getImportContext imports query =
135- everythingBut
161+ gather
136162 (<>)
137163 ((mempty , False ) `mkQ` importQ query)
138164 imports
139165
140166getDeclContext :: LHsDecl GhcPs -> Range -> ContextResult
141167getDeclContext 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.
154185getContext :: 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+
203237sigQ :: Range -> LSig GhcPs -> (ContextResult , Bool )
204238sigQ = 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)
0 commit comments