@@ -13,15 +13,14 @@ module Development.IDE.Plugin.Completions.Context
1313 ) where
1414
1515import Control.DeepSeq (NFData (.. ), rwhnf )
16+ import Data.Generics (extQ , mkQ )
17+ import Data.Generics.Schemes (everythingBut )
1618import Data.Hashable (Hashable )
17- import Data.List (maximumBy )
18- import Data.Maybe (catMaybes , mapMaybe )
19- import Data.Ord (Down (.. ), comparing )
19+ import Data.Maybe (mapMaybe )
2020import qualified Data.Text as T
2121import Development.IDE
2222import Development.IDE.Core.PositionMapping
2323import Development.IDE.GHC.Compat hiding (getContext )
24- import Development.IDE.GHC.Compat.Util (bagToList )
2524import GHC.Generics (Generic )
2625import GHC.Hs (HasLoc )
2726
@@ -76,34 +75,46 @@ instance Hashable GetContextMap
7675instance NFData GetContextMap
7776type instance RuleResult GetContextMap = ContextMap
7877
79- -- | A lazy chunked interval structure for context lookups.
80- --
81- -- Entries within each chunk are from a contiguous group of source items
78+ -- | Entries within each chunk are from a contiguous group of source items
8279-- (imports or declarations).
8380data ContextChunk = Chunk
84- { low :: {-# UNPACK #-} ! Position
85- , high :: {-# UNPACK #-} ! Position
86- , group :: {-# UNPACK #-} ! ContextGroup
87- , items :: [( Range , Context )]
81+ { low :: {-# UNPACK #-} ! Position
82+ , high :: {-# UNPACK #-} ! Position
83+ , group :: {-# UNPACK #-} ! ContextGroup
84+ , context :: Range -> ContextResult
8885 }
8986
9087-- | Build lazy 'ContextChunk' by processing @n@ source items at a time.
91- groupedChunks :: Int -> ContextGroup -> (a -> Maybe Range ) -> (a -> [( Range , Context )] ) -> [a ] -> ContextMap
92- groupedChunks n group getPos getRanges xs = ContextMap $ go xs
88+ groupedChunks :: Int -> ContextGroup -> (a -> Maybe Range ) -> (a -> Range -> ContextResult ) -> [a ] -> ContextMap
89+ groupedChunks n group getPos locate xs = ContextMap $ go xs
9390 where
9491 go [] = []
9592 go xs =
9693 let (chunk, rest) = splitAt n xs
97- items = concatMap getRanges chunk
98- in case items of
94+ context = foldMap locate chunk
95+ in case chunk of
9996 [] -> go rest
10097 _ -> Chunk
10198 { low = minimum (mapMaybe (fmap _start . getPos) chunk)
10299 , high = maximum (mapMaybe (fmap _end . getPos) chunk)
103100 , group
104- , items
101+ , context
105102 } : go rest
106103
104+ data ContextResult
105+ = NoContext
106+ | ContextResult Range Context
107+
108+ instance Semigroup ContextResult where
109+ NoContext <> b = b
110+ a <> NoContext = a
111+ ar@ (ContextResult a _) <> br@ (ContextResult b _) = if a `dominates` b
112+ then br
113+ else ar
114+
115+ instance Monoid ContextResult where
116+ mempty = NoContext
117+
107118newtype ContextMap = ContextMap [ContextChunk ]
108119 deriving newtype (Monoid , Semigroup )
109120instance Show ContextMap where show _ = " <context map>"
@@ -116,119 +127,96 @@ instance NFData ContextMap where rnf = rwhnf
116127-- as a Shake rule.
117128getContextMap :: ParsedModule -> ContextMap
118129getContextMap pm =
119- groupedChunks 10 ImportGroup rangeOf importEntry hsmodImports
120- <> groupedChunks 10 DeclarationGroup rangeOf declEntry hsmodDecls
130+ groupedChunks 10 ImportGroup rangeOf getImportContext hsmodImports
131+ <> groupedChunks 5 DeclarationGroup rangeOf getDeclContext hsmodDecls
121132 where
122133 HsModule {hsmodImports, hsmodDecls} =
123134 unLoc (pm_parsed_source pm)
124135
125- rangeOf :: HasLoc (Anno a ) => XRec GhcPs a -> Maybe Range
126- rangeOf (L (locA -> ss) _) = srcSpanToRange ss
127- fromSpan context ss = (,context) <$> rangeOf ss
128-
129- importEntry :: LImportDecl GhcPs -> [(Range , Context )]
130- importEntry decl@ (L _ impDecl) =
131- let modName = T. pack $ moduleNameString $ unLoc $ ideclName impDecl
132- outerCtx = fromSpan (ImportContext modName) decl
133- innerCtx = importListEntry modName (fmap (fmap reLoc) $ ideclImportList impDecl)
134- in catMaybes [outerCtx, innerCtx]
135-
136- importListEntry modName (Just (EverythingBut , imps)) = fromSpan (ImportHidingContext modName) imps
137- importListEntry modName (Just (Exactly , imps)) = fromSpan (ImportHidingContext modName) imps
138- importListEntry _ _ = Nothing
139-
140- declEntry :: LHsDecl GhcPs -> [(Range , Context )]
141- declEntry (L (locA -> ss) decl) = case srcSpanToRange ss of
142- Nothing -> []
143- Just range -> case decl of
144- SigD {} -> [(range, TypeContext )]
145- ValD _ bind -> (range, ValueContext ) : bindEntries bind
146- TyClD _ cd@ ClassDecl {} -> (range, TypeContext ) : classEntries cd
147- TyClD {} -> [(range, TypeContext )] -- DataDecl, SynDecl, FamilyDecl
148- InstD _ instDecl -> (range, ValueContext ) : instEntries instDecl
149- DerivD {} -> [(range, TypeContext )]
150- ForD {} -> [(range, ValueContext )]
151- SpliceD {} -> [(range, TopContext [] )]
152- _ -> [(range, DefaultContext )] -- DefD, WarningD, AnnD, RuleD, DocD, KindSigD
153-
154- sigsAndBindEntries :: [LSig GhcPs ] -> LHsBinds GhcPs -> [(Range , Context )]
155- sigsAndBindEntries sigs binds =
156- [ (r, TypeContext )
157- | L (locA -> ss) _ <- sigs
158- , Just r <- [srcSpanToRange ss]
159- ] ++
160- [ entry
161- | L (locA -> ss) bind <- bagToList binds
162- , Just r <- [srcSpanToRange ss]
163- , entry <- (r, ValueContext ) : bindEntries bind
164- ]
165-
166- classEntries :: TyClDecl GhcPs -> [(Range , Context )]
167- classEntries ClassDecl { tcdSigs, tcdMeths } = sigsAndBindEntries tcdSigs tcdMeths
168- classEntries _ = []
169-
170- instEntries :: InstDecl GhcPs -> [(Range , Context )]
171- instEntries ClsInstD { cid_inst = ClsInstDecl { cid_sigs, cid_binds } } =
172- sigsAndBindEntries cid_sigs cid_binds
173- instEntries _ = []
174-
175- bindEntries :: HsBind GhcPs -> [(Range , Context )]
176- bindEntries FunBind { fun_matches = MG { mg_alts = L _ alts } } =
177- concatMap matchLocalEntries alts
178- bindEntries PatBind { pat_rhs = GRHSs { grhssLocalBinds, grhssGRHSs } } =
179- localBindEntries grhssLocalBinds
180- ++ concatMap exprLocalEntries [ body | L _ (GRHS _ _ body) <- grhssGRHSs ]
181- bindEntries _ = []
182-
183- matchLocalEntries :: LMatch GhcPs (LHsExpr GhcPs ) -> [(Range , Context )]
184- matchLocalEntries (L _ Match { m_grhss = GRHSs { grhssLocalBinds, grhssGRHSs } }) =
185- localBindEntries grhssLocalBinds
186- ++ concatMap exprLocalEntries [ body | L _ (GRHS _ _ body) <- grhssGRHSs ]
187-
188- localBindEntries :: HsLocalBinds GhcPs -> [(Range , Context )]
189- localBindEntries (HsValBinds _ (ValBinds _ binds sigs)) =
190- sigsAndBindEntries sigs binds
191- localBindEntries _ = []
192-
193- exprLocalEntries :: LHsExpr GhcPs -> [(Range , Context )]
194- exprLocalEntries (L _ expr) = case expr of
195- #if !MIN_VERSION_ghc(9,9,0)
196- HsLet _ _ binds _ body -> localBindEntries binds ++ exprLocalEntries body
197- #else
198- HsLet _ binds body -> localBindEntries binds ++ exprLocalEntries body
199- #endif
200- HsDo _ _ stmts ->
201- [ entry
202- | L _ (LetStmt _ lbs) <- unLoc stmts
203- , entry <- localBindEntries lbs
204- ]
205- _ -> []
136+ rangeOf :: HasLoc a => a -> Maybe Range
137+ rangeOf = srcSpanToRange . locA
138+
139+ contextual :: HasLoc a => Context -> Bool -> Range -> a -> (ContextResult , Bool )
140+ contextual context shouldStop query s =
141+ let range = rangeOf s
142+ in case range of
143+ Nothing -> (mempty , True )
144+ Just range | outside query range -> (mempty , True )
145+ Just range -> (ContextResult range context, shouldStop)
146+
147+ getImportContext :: LImportDecl GhcPs -> Range -> ContextResult
148+ getImportContext imports query =
149+ everythingBut
150+ (<>)
151+ ((mempty , False ) `mkQ` importQ query)
152+ imports
153+
154+ getDeclContext :: LHsDecl GhcPs -> Range -> ContextResult
155+ getDeclContext declarations query =
156+ everythingBut
157+ (<>)
158+ ((mempty , False ) `mkQ` sigQ query `extQ` bindQ query `extQ` declQ query)
159+ declarations
160+
161+ importQ :: Range -> LImportDecl GhcPs -> (ContextResult , Bool )
162+ importQ query impDecl'@ (L _ impDecl) =
163+ let importModuleName = T. pack $ moduleNameString $ unLoc $ ideclName impDecl
164+ inlineResults = fst $ importInline query importModuleName (ideclImportList impDecl)
165+ importResult = fst $ contextual (ImportContext importModuleName) True query impDecl'
166+ importInline _ _ Nothing = (mempty , False )
167+ importInline query modName (Just (which, l)) =
168+ case which of
169+ EverythingBut -> contextual (ImportHidingContext modName) True query l
170+ Exactly -> contextual (ImportListContext modName) True query l
171+ in (inlineResults <> importResult, False )
172+
173+
174+ declQ :: Range -> LHsDecl GhcPs -> (ContextResult , Bool )
175+ declQ query (L (locA -> ss) decl) = case srcSpanToRange ss of
176+ Nothing -> (mempty , True )
177+ Just range | outside query range -> (mempty , True )
178+ Just range -> case decl of
179+ SigD {} -> (ContextResult range TypeContext , True )
180+ ValD {} -> (ContextResult range ValueContext , False )
181+ TyClD {} -> (ContextResult range TypeContext , False ) -- DataDecl, SynDecl, FamilyDecl
182+ InstD {} -> (ContextResult range ValueContext , False )
183+ DerivD {} -> (ContextResult range TypeContext , True )
184+ SpliceD {} -> (ContextResult range (TopContext [] ), True )
185+ _ -> (ContextResult range DefaultContext , True ) -- DefD, WarningD, AnnD, RuleD, DocD, KindSigD
186+
187+ sigQ :: Range -> LSig GhcPs -> (ContextResult , Bool )
188+ sigQ = contextual TypeContext True
189+
190+ bindQ :: Range -> LHsBind GhcPs -> (ContextResult , Bool )
191+ bindQ = contextual ValueContext False
192+
193+ dominates :: Range -> Range -> Bool
194+ dominates (Range s e) (Range qs qe) = s <= qs && qe <= e
195+
196+ outside :: Range -> Range -> Bool
197+ outside (Range ps pe) (Range qs qe) = pe < qs || ps > qe
206198
207199-- | Look up the completion context at a given position.
208200-- Returns the innermost (most specific) context that contains the position.
209201--
210202-- Only the 'ContextChunks' up to and including the chunk containing the
211203-- query position are forced; later chunks remain as unevaluated thunks.
212204getContext :: ContextMap -> PositionResult Position -> Context
213- getContext (ContextMap chunks) pos =
205+ getContext (ContextMap chunks) query =
214206 case searchChunks True chunks of
215- ([] , [] ) -> TopContext []
216- (groups, [] ) -> TopContext groups
217- (_, xs) -> snd $ maximumBy (comparing (\ (Range s e, _) -> (s, Down e))) xs
207+ (groups, NoContext ) -> TopContext groups
208+ (_, ContextResult _ found) -> found
218209 where
219- (qLo, qHi) = case pos of
210+ (qLo, qHi) = case query of
220211 PositionExact p -> (p, p)
221212 PositionRange l u -> (l, u)
222213
223- dominates :: (Range , Context ) -> Bool
224- dominates (Range s e, _) = s <= qLo && qHi <= e
225-
226- searchChunks :: Bool -> [ContextChunk ] -> ([ContextGroup ], [(Range , Context )])
227- searchChunks _ [] = ([] , [] )
228- searchChunks firstChunk (Chunk cLo cHi group items : rest)
214+ searchChunks :: Bool -> [ContextChunk ] -> ([ContextGroup ], ContextResult )
215+ searchChunks _ [] = ([] , mempty )
216+ searchChunks firstChunk (Chunk cLo cHi group contextOf : rest)
229217 | -- query is past this chunk
230218 qLo > cHi = searchChunks False rest
231219 | -- query is before this chunk
232- qHi < cLo = (if firstChunk then [HeaderGroup ] else [] , [] )
220+ qHi < cLo = (if firstChunk then [HeaderGroup ] else [] , mempty )
233221 -- this chunk is relevant, emit the group and all relevant intervals
234- | otherwise = ([group], filter dominates items ) <> searchChunks False rest
222+ | otherwise = ([group], contextOf ( Range qLo qHi) ) <> searchChunks False rest
0 commit comments