@@ -8,15 +8,24 @@ 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 )
23+ import Data.List.Extra (nubOrd )
24+ import Data.Maybe (isJust , mapMaybe ,
25+ maybeToList )
26+ import Data.List.Extra (nub )
27+ import Data.Maybe (isJust , mapMaybe ,
28+ maybeToList )
2029import qualified Data.Text as T
2130import Development.IDE
2231import Development.IDE.Core.PositionMapping
@@ -68,31 +77,38 @@ type instance RuleResult GetContextMap = ContextMap
6877data ContextChunk = Chunk
6978 { low :: {-# UNPACK #-} ! Position
7079 , high :: {-# UNPACK #-} ! Position
71- , group :: {-# UNPACK #-} ! ContextGroup
80+ , group :: ! ContextGroup
7281 , context :: Range -> ContextResult
7382 }
7483
7584-- | 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
85+ groupedChunks :: Int -> ContextGroup -> (a -> Maybe Range ) -> (a -> Range -> ContextResult ) -> [a ] -> [ ContextChunk ]
86+ groupedChunks n group getPos locate xs = go xs
7887 where
7988 go [] = []
8089 go xs =
8190 let (chunk, rest) = splitAt n xs
8291 context = foldMap locate chunk
83- in case chunk of
92+ positions = mapMaybe getPos chunk
93+ in case positions of
8494 [] -> go rest
85- _ -> Chunk
86- { low = minimum (mapMaybe ( fmap _start . getPos) chunk )
87- , high = maximum (mapMaybe ( fmap _end . getPos) chunk )
95+ ps -> Chunk
96+ { low = minimum (fmap _start ps )
97+ , high = maximum (fmap _end ps )
8898 , group
8999 , context
90100 } : go rest
91101
102+ -- | Build lazy 'ContextChunk' by processing @n@ source items at a time.
103+ singletonChunk :: ContextGroup -> (a -> Maybe Range ) -> (a -> Range -> ContextResult ) -> a -> ContextChunk
104+ singletonChunk group getPos locate inp = Chunk s e group (locate inp)
105+ where
106+ Range s e = fromJust $ getPos inp
107+
92108-- | Used during context finding, combines into the tightest interval.
93109-- As an intuition, the primary interface is through
94- -- @Monoid (Position -> ContextResult)@.
95- data ContextResult = NoContext | ContextResult Range Context
110+ -- @Monoid (Range -> ContextResult)@.
111+ data ContextResult = NoContext | ContextResult ! Range ! Context
96112instance Monoid ContextResult where mempty = NoContext
97113instance Semigroup ContextResult where (<>) = tighten
98114
@@ -102,8 +118,14 @@ tighten a NoContext = a
102118tighten ar@ (ContextResult a _) br@ (ContextResult b _) =
103119 if a `dominates` b then br else ar
104120
105- newtype ContextMap = ContextMap [ContextChunk ]
106- deriving newtype (Monoid , Semigroup )
121+ -- | A context map, built from a parsed module. Stores whether the module
122+ -- already has a @module ... where@ header, so that the header snippet can
123+ -- be suppressed for files that already declare a module.
124+ data ContextMap = ContextMap ! Bool [ContextChunk ]
125+ instance Semigroup ContextMap where
126+ ContextMap h1 c1 <> ContextMap h2 c2 = ContextMap (h1 || h2) (c1 <> c2)
127+ instance Monoid ContextMap where
128+ mempty = ContextMap False []
107129instance Show ContextMap where show _ = " <context map>"
108130instance NFData ContextMap where rnf = rwhnf
109131
@@ -116,10 +138,14 @@ instance NFData ContextMap where rnf = rwhnf
116138-- as a Shake rule.
117139getContextMap :: ParsedModule -> ContextMap
118140getContextMap pm =
119- groupedChunks 10 ImportGroup rangeOf getImportContext hsmodImports
120- <> groupedChunks 5 DeclarationGroup rangeOf getDeclContext hsmodDecls
141+ ContextMap (isJust hsmodName) $
142+ -- These denote the size of the "jumps" of the cursor when traversing the AST.
143+ -- Reduces the amount of data we have to look at with syb.
144+ moduleChunk
145+ <> groupedChunks 10 ImportGroup rangeOf getImportContext hsmodImports
146+ <> groupedChunks 4 DeclarationGroup rangeOf getDeclContext hsmodDecls
121147 where
122- HsModule {hsmodImports, hsmodDecls} =
148+ HsModule {hsmodName, hsmodImports, hsmodDecls} =
123149 unLoc (pm_parsed_source pm)
124150
125151#if MIN_VERSION_ghc(9,9,0)
@@ -130,47 +156,59 @@ rangeOf :: GenLocated (SrcSpanAnn' a) e -> Maybe Range
130156rangeOf = srcSpanToRange . getLocA
131157#endif
132158
159+ getHeaderContext :: Data a => a -> Range -> ContextResult
160+ getHeaderContext decl query =
161+ gather
162+ (<>)
163+ ((mempty , False ) `mkQ` modNameQ query)
164+ decl
165+
133166getImportContext :: LImportDecl GhcPs -> Range -> ContextResult
134167getImportContext imports query =
135- everythingBut
168+ gather
136169 (<>)
137170 ((mempty , False ) `mkQ` importQ query)
138171 imports
139172
140173getDeclContext :: LHsDecl GhcPs -> Range -> ContextResult
141174getDeclContext declarations query =
142- everythingBut
175+ gather
143176 (<>)
144177 ((mempty , False ) `mkQ` sigQ query `extQ` bindQ query `extQ` declQ query)
145178 declarations
146179
147180-- * Querying
148181
182+ -- | Returns 'True' when the parsed module already has a @module ... where@
183+ -- declaration. Used downstream to suppress the module header snippet.
184+ contextHasModuleHeader :: ContextMap -> Bool
185+ contextHasModuleHeader (ContextMap h _) = h
186+
149187-- | Look up the completion context at a given position.
150188-- Returns the innermost (most specific) context that contains the position.
151189--
152190-- Only the 'ContextChunks' up to and including the chunk containing the
153191-- query position are forced; later chunks remain as unevaluated thunks.
154192getContext :: ContextMap -> PositionResult Position -> Context
155- getContext (ContextMap chunks) query =
156- case searchChunks True chunks of
157- (groups, NoContext ) -> TopContext groups
193+ getContext (ContextMap _ chunks) query =
194+ case searchChunks HeaderGroup chunks mempty of
195+ (groups, NoContext ) -> TopContext $ nub groups
158196 (_, ContextResult _ found) -> found
159197 where
160198 (qLo, qHi) = case query of
161199 PositionExact p -> (p, p)
162200 PositionRange l u -> (l, u)
163201
164- searchChunks :: Bool -> [ContextChunk ] -> ([ContextGroup ], ContextResult )
165- searchChunks _ [] = ( [] , mempty )
166- searchChunks firstChunk (Chunk cLo cHi group contextOf : rest)
202+ searchChunks :: ContextGroup -> [ContextChunk ] -> ([ ContextGroup ], ContextResult ) -> ([ContextGroup ], ContextResult )
203+ searchChunks _ [] ! acc = acc
204+ searchChunks lastChunk (Chunk cLo cHi group contextOf : rest) ! acc
167205 | -- query is past this chunk (line-only comparison so cursors
168206 -- past the last column on the final line still match)
169- _line qLo > _line cHi = searchChunks False rest
207+ _line qLo > _line cHi = searchChunks group rest acc
170208 | -- query is before this chunk
171- qHi < cLo = (if firstChunk then [ HeaderGroup ] else [ ] , mempty )
209+ qHi < cLo = ([lastChunk, group ], mempty ) <> acc
172210 -- this chunk is relevant, emit the group and all relevant intervals
173- | otherwise = ( [group], contextOf (Range qLo qHi)) <> searchChunks False rest
211+ | otherwise = searchChunks group rest (( [group], contextOf (Range qLo qHi)) <> acc)
174212
175213-- * SYB queries types
176214
@@ -200,6 +238,9 @@ declQ query (L (locA -> ss) decl) = case srcSpanToRange ss of
200238 SpliceD {} -> (ContextResult range (TopContext [] ), True )
201239 _ -> (ContextResult range DefaultContext , True ) -- DefD, WarningD, AnnD, RuleD, DocD, KindSigD
202240
241+ modNameQ :: Range -> XRec GhcPs ModuleName -> (ContextResult , Bool )
242+ modNameQ = contextual (TopContext [HeaderGroup ]) True
243+
203244sigQ :: Range -> LSig GhcPs -> (ContextResult , Bool )
204245sigQ = contextual TypeContext True
205246
@@ -247,3 +288,13 @@ instance Pretty ContextGroup where
247288 HeaderGroup -> " header"
248289 ImportGroup -> " imports"
249290 DeclarationGroup -> " declarations"
291+
292+ -- | Variation of @Data.Generics.Schemes.everythingBut@, but uses foldl'.
293+ gather :: forall r . (r -> r -> r ) -> GenericQ (r , Bool ) -> GenericQ r
294+ gather k f = go
295+ where
296+ go :: GenericQ r
297+ go x = let (v, stop) = f x
298+ in if stop
299+ then v
300+ else foldl' k v (gmapQ go x)
0 commit comments