@@ -19,11 +19,10 @@ module Development.IDE.Plugin.CodeAction
1919
2020import Control.Applicative ((<|>) )
2121import Control.Arrow (second ,
22- (>>> ) ,
23- (&&& ) )
22+ (&&& ) ,
23+ (>>> ) )
2424import Control.Concurrent.STM.Stats (atomically )
25- import Control.Monad (guard , join ,
26- msum )
25+ import Control.Monad (guard , join )
2726import Control.Monad.IO.Class
2827import Data.Char
2928import qualified Data.DList as DL
@@ -34,7 +33,7 @@ import qualified Data.HashSet as Set
3433import Data.List.Extra
3534import Data.List.NonEmpty (NonEmpty ((:|) ))
3635import qualified Data.List.NonEmpty as NE
37- import qualified Data.Map as M
36+ import qualified Data.Map.Strict as M
3837import Data.Maybe
3938import Data.Ord (comparing )
4039import qualified Data.Rope.UTF16 as Rope
@@ -47,7 +46,6 @@ import Development.IDE.Core.Service
4746import Development.IDE.GHC.Compat
4847import Development.IDE.GHC.Compat.Util
4948import Development.IDE.GHC.Error
50- import Development.IDE.GHC.ExactPrint
5149import Development.IDE.GHC.Util (printOutputable ,
5250 printRdrName ,
5351 traceAst )
@@ -80,6 +78,25 @@ import Language.LSP.Types (CodeAction (
8078import Language.LSP.VFS
8179import Text.Regex.TDFA (mrAfter ,
8280 (=~) , (=~~) )
81+ #if MIN_VERSION_ghc(9,2,0)
82+ import GHC (AddEpAnn (AddEpAnn ),
83+ Anchor (anchor_op ),
84+ AnchorOperation (.. ),
85+ AnnsModule (am_main ),
86+ DeltaPos (.. ),
87+ EpAnn (.. ),
88+ EpaLocation (.. ),
89+ LEpaComment ,
90+ LocatedA )
91+
92+ import Control.Monad (msum )
93+ #else
94+ import Language.Haskell.GHC.ExactPrint.Types (Annotation (annsDP ),
95+ DeltaPos ,
96+ KeywordId (G ),
97+ deltaRow ,
98+ mkAnnKey )
99+ #endif
83100
84101-------------------------------------------------------------------------------------------------
85102
@@ -227,10 +244,8 @@ findInstanceHead df instanceHead decls =
227244
228245#if MIN_VERSION_ghc(9,2,0)
229246findDeclContainingLoc :: Foldable t => Position -> t (GenLocated (SrcSpanAnn' a ) e ) -> Maybe (GenLocated (SrcSpanAnn' a ) e )
230- #elif MIN_VERSION_ghc(8,10,0)
231- findDeclContainingLoc :: Foldable t => Position -> t (GenLocated SrcSpan e ) -> Maybe (GenLocated SrcSpan e )
232247#else
233- -- TODO populate this type signature for GHC versions <8.10
248+ findDeclContainingLoc :: Foldable t => Position -> t ( GenLocated SrcSpan e ) -> Maybe ( GenLocated SrcSpan e )
234249#endif
235250findDeclContainingLoc loc = find (\ (L l _) -> loc `isInsideSrcSpan` locA l)
236251
@@ -243,8 +258,8 @@ findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` locA l)
243258-- imported from ‘Data.ByteString’ at B.hs:6:1-22
244259-- imported from ‘Data.ByteString.Lazy’ at B.hs:8:1-27
245260-- imported from ‘Data.Text’ at B.hs:7:1-16
246- suggestHideShadow :: ParsedSource -> T. Text -> Maybe TcModuleResult -> Maybe HieAstResult -> Diagnostic -> [(T. Text , [Either TextEdit Rewrite ])]
247- suggestHideShadow ps@ ( L _ HsModule {hsmodImports}) fileContents mTcM mHar Diagnostic {_message, _range}
261+ suggestHideShadow :: Annotated ParsedSource -> T. Text -> Maybe TcModuleResult -> Maybe HieAstResult -> Diagnostic -> [(T. Text , [Either TextEdit Rewrite ])]
262+ suggestHideShadow ps fileContents mTcM mHar Diagnostic {_message, _range}
248263 | Just [identifier, modName, s] <-
249264 matchRegexUnifySpaces
250265 _message
@@ -261,6 +276,8 @@ suggestHideShadow ps@(L _ HsModule {hsmodImports}) fileContents mTcM mHar Diagno
261276 result <> [hideAll]
262277 | otherwise = []
263278 where
279+ L _ HsModule {hsmodImports} = astA ps
280+
264281 suggests identifier modName s
265282 | Just tcM <- mTcM,
266283 Just har <- mHar,
@@ -940,11 +957,11 @@ isPreludeImplicit = xopt Lang.ImplicitPrelude
940957suggestImportDisambiguation ::
941958 DynFlags ->
942959 Maybe T. Text ->
943- ParsedSource ->
960+ Annotated ParsedSource ->
944961 T. Text ->
945962 Diagnostic ->
946963 [(T. Text , [Either TextEdit Rewrite ])]
947- suggestImportDisambiguation df (Just txt) ps@ ( L _ HsModule {hsmodImports}) fileContents diag@ Diagnostic {.. }
964+ suggestImportDisambiguation df (Just txt) ps fileContents diag@ Diagnostic {.. }
948965 | Just [ambiguous] <-
949966 matchRegexUnifySpaces
950967 _message
@@ -956,6 +973,8 @@ suggestImportDisambiguation df (Just txt) ps@(L _ HsModule {hsmodImports}) fileC
956973 suggestions ambiguous modules (isJust local)
957974 | otherwise = []
958975 where
976+ L _ HsModule {hsmodImports} = astA ps
977+
959978 locDic =
960979 fmap (NE. fromList . DL. toList) $
961980 Map. fromListWith (<>) $
@@ -1048,21 +1067,21 @@ targetModuleName (ExistingImp _) =
10481067 error " Cannot happen!"
10491068
10501069disambiguateSymbol ::
1051- ParsedSource ->
1070+ Annotated ParsedSource ->
10521071 T. Text ->
10531072 Diagnostic ->
10541073 T. Text ->
10551074 HidingMode ->
10561075 [Either TextEdit Rewrite ]
1057- disambiguateSymbol pm fileContents Diagnostic {.. } (T. unpack -> symbol) = \ case
1076+ disambiguateSymbol ps fileContents Diagnostic {.. } (T. unpack -> symbol) = \ case
10581077 (HideOthers hiddens0) ->
10591078 [ Right $ hideSymbol symbol idecl
10601079 | ExistingImp idecls <- hiddens0
10611080 , idecl <- NE. toList idecls
10621081 ]
10631082 ++ mconcat
10641083 [ if null imps
1065- then maybeToList $ Left . snd <$> newImportToEdit (hideImplicitPreludeSymbol $ T. pack symbol) pm fileContents
1084+ then maybeToList $ Left . snd <$> newImportToEdit (hideImplicitPreludeSymbol $ T. pack symbol) ps fileContents
10661085 else Right . hideSymbol symbol <$> imps
10671086 | ImplicitPrelude imps <- hiddens0
10681087 ]
@@ -1292,7 +1311,7 @@ removeRedundantConstraints df (L _ HsModule {hsmodDecls}) Diagnostic{..}
12921311
12931312-------------------------------------------------------------------------------------------------
12941313
1295- suggestNewOrExtendImportForClassMethod :: ExportsMap -> ParsedSource -> T. Text -> Diagnostic -> [(T. Text , CodeActionKind , [Either TextEdit Rewrite ])]
1314+ suggestNewOrExtendImportForClassMethod :: ExportsMap -> Annotated ParsedSource -> T. Text -> Diagnostic -> [(T. Text , CodeActionKind , [Either TextEdit Rewrite ])]
12961315suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnostic {_message}
12971316 | Just [methodName, className] <-
12981317 matchRegexUnifySpaces
@@ -1306,7 +1325,7 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
13061325 where
13071326 suggest identInfo@ IdentInfo {moduleNameText}
13081327 | importStyle <- NE. toList $ importStyles identInfo,
1309- mImportDecl <- findImportDeclByModuleName (hsmodImports $ unLoc ps) (T. unpack moduleNameText) =
1328+ mImportDecl <- findImportDeclByModuleName (hsmodImports . unLoc . astA $ ps) (T. unpack moduleNameText) =
13101329 case mImportDecl of
13111330 -- extend
13121331 Just decl ->
@@ -1328,8 +1347,8 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
13281347 <> [(quickFixImportKind " new.all" , newImportAll moduleNameText)]
13291348 | otherwise -> []
13301349
1331- suggestNewImport :: ExportsMap -> ParsedSource -> T. Text -> Diagnostic -> [(T. Text , CodeActionKind , TextEdit )]
1332- suggestNewImport packageExportsMap ps@ ( L _ HsModule { .. }) fileContents Diagnostic {_message}
1350+ suggestNewImport :: ExportsMap -> Annotated ParsedSource -> T. Text -> Diagnostic -> [(T. Text , CodeActionKind , TextEdit )]
1351+ suggestNewImport packageExportsMap ps fileContents Diagnostic {_message}
13331352 | msg <- unifySpaces _message
13341353 , Just thingMissing <- extractNotInScopeName msg
13351354 , qual <- extractQualifiedModuleName msg
@@ -1344,6 +1363,8 @@ suggestNewImport packageExportsMap ps@(L _ HsModule {..}) fileContents Diagnosti
13441363 = sortOn fst3 [(imp, kind, TextEdit range (imp <> " \n " <> T. replicate indent " " ))
13451364 | (kind, unNewImport -> imp) <- constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions
13461365 ]
1366+ where
1367+ L _ HsModule {.. } = astA ps
13471368suggestNewImport _ _ _ _ = []
13481369
13491370constructNewImportSuggestions
@@ -1371,7 +1392,7 @@ constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules =
13711392newtype NewImport = NewImport { unNewImport :: T. Text}
13721393 deriving (Show , Eq , Ord )
13731394
1374- newImportToEdit :: NewImport -> ParsedSource -> T. Text -> Maybe (T. Text , TextEdit )
1395+ newImportToEdit :: NewImport -> Annotated ParsedSource -> T. Text -> Maybe (T. Text , TextEdit )
13751396newImportToEdit (unNewImport -> imp) ps fileContents
13761397 | Just (range, indent) <- newImportInsertRange ps fileContents
13771398 = Just (imp, TextEdit range (imp <> " \n " <> T. replicate indent " " ))
@@ -1385,35 +1406,105 @@ newImportToEdit (unNewImport -> imp) ps fileContents
13851406-- * If the file has neither existing imports nor a module declaration,
13861407-- the import will be inserted at line zero if there are no pragmas,
13871408-- * otherwise inserted one line after the last file-header pragma
1388- newImportInsertRange :: ParsedSource -> T. Text -> Maybe (Range , Int )
1389- newImportInsertRange ( L _ HsModule { .. }) fileContents
1409+ newImportInsertRange :: Annotated ParsedSource -> T. Text -> Maybe (Range , Int )
1410+ newImportInsertRange ps fileContents
13901411 | Just ((l, c), col) <- case hsmodImports of
1391- [] -> findPositionNoImports (fmap reLoc hsmodName) (fmap reLoc hsmodExports) fileContents
1392- _ -> findPositionFromImportsOrModuleDecl (map reLoc hsmodImports) last True
1412+ -- When there is no existing imports, we only cares about the line number, setting column and indent to zero.
1413+ [] -> (\ line -> ((line, 0 ), 0 )) <$> findPositionNoImports ps fileContents
1414+ _ -> findPositionFromImports (map reLoc hsmodImports) last
13931415 , let insertPos = Position (fromIntegral l) (fromIntegral c)
13941416 = Just (Range insertPos insertPos, col)
13951417 | otherwise = Nothing
1418+ where
1419+ L _ HsModule {.. } = astA ps
1420+
1421+ -- | Find the position for a new import when there isn't an existing one.
1422+ -- * If there is a module declaration, a new import should be inserted under the module declaration (including exports list)
1423+ -- * Otherwise, a new import should be inserted after any file-header pragma.
1424+ findPositionNoImports :: Annotated ParsedSource -> T. Text -> Maybe Int
1425+ findPositionNoImports ps fileContents =
1426+ maybe (Just (findNextPragmaPosition fileContents)) (findPositionAfterModuleName ps) hsmodName
1427+ where
1428+ L _ HsModule {.. } = astA ps
13961429
1397- -- | Insert the import under the Module declaration exports if they exist, otherwise just under the module declaration.
1398- -- If no module declaration exists, then no exports will exist either, in that case
1399- -- insert the import after any file-header pragmas or at position zero if there are no pragmas
1400- findPositionNoImports :: Maybe (Located ModuleName ) -> Maybe (Located [LIE name ]) -> T. Text -> Maybe ((Int , Int ), Int )
1401- findPositionNoImports Nothing _ fileContents = findNextPragmaPosition fileContents
1402- findPositionNoImports _ (Just hsmodExports) _ = findPositionFromImportsOrModuleDecl hsmodExports id False
1403- findPositionNoImports (Just hsmodName) _ _ = findPositionFromImportsOrModuleDecl hsmodName id False
1430+ -- | find line number right after module ... where
1431+ findPositionAfterModuleName :: Annotated ParsedSource
1432+ #if MIN_VERSION_ghc(9,2,0)
1433+ -> LocatedA ModuleName
1434+ #else
1435+ -> Located ModuleName
1436+ #endif
1437+ -> Maybe Int
1438+ findPositionAfterModuleName ps hsmodName' = do
1439+ -- Note that 'where' keyword and comments are not part of the AST. They belongs to
1440+ -- the exact-print information. To locate it, we need to find the previous AST node,
1441+ -- calculate the gap between it and 'where', then add them up to produce the absolute
1442+ -- position of 'where'.
1443+
1444+ lineOffset <- whereKeywordLineOffset -- Calculate the gap before 'where' keyword.
1445+ case prevSrcSpan of
1446+ UnhelpfulSpan _ -> Nothing
1447+ (RealSrcSpan prevSrcSpan' _) ->
1448+ -- add them up produce the absolute location of 'where' keyword
1449+ Just $ srcLocLine (realSrcSpanEnd prevSrcSpan') + lineOffset
1450+ where
1451+ L _ HsModule {.. } = astA ps
1452+
1453+ -- The last AST node before 'where' keyword. Might be module name or export list.
1454+ prevSrcSpan = maybe (getLoc hsmodName') getLoc hsmodExports
1455+
1456+ -- The relative position of 'where' keyword (in lines, relative to the previous AST node).
1457+ -- The exact-print API changed a lot in ghc-9.2, so we need to handle it seperately for different compiler versions.
1458+ whereKeywordLineOffset :: Maybe Int
1459+ #if MIN_VERSION_ghc(9,2,0)
1460+ whereKeywordLineOffset = case hsmodAnn of
1461+ EpAnn _ annsModule _ -> do
1462+ -- Find the first 'where'
1463+ whereLocation <- fmap NE. head . NE. nonEmpty . mapMaybe filterWhere . am_main $ annsModule
1464+ epaLocationToLine whereLocation
1465+ EpAnnNotUsed -> Nothing
1466+ filterWhere (AddEpAnn AnnWhere loc) = Just loc
1467+ filterWhere _ = Nothing
1468+
1469+ epaLocationToLine :: EpaLocation -> Maybe Int
1470+ epaLocationToLine (EpaSpan sp) = Just . srcLocLine . realSrcSpanEnd $ sp
1471+ epaLocationToLine (EpaDelta (SameLine _) priorComments) = Just $ sumCommentsOffset priorComments
1472+ -- 'priorComments' contains the comments right before the current EpaLocation
1473+ -- Summing line offset of priorComments is necessary, as 'line' is the gap between the last comment and
1474+ -- the current AST node
1475+ epaLocationToLine (EpaDelta (DifferentLine line _) priorComments) = Just (line + sumCommentsOffset priorComments)
1476+
1477+ sumCommentsOffset :: [LEpaComment ] -> Int
1478+ sumCommentsOffset = sum . fmap (\ (L anchor _) -> anchorOpLine (anchor_op anchor))
1479+
1480+ anchorOpLine :: AnchorOperation -> Int
1481+ anchorOpLine UnchangedAnchor = 0
1482+ anchorOpLine (MovedAnchor (SameLine _)) = 0
1483+ anchorOpLine (MovedAnchor (DifferentLine line _)) = line
1484+ #else
1485+ whereKeywordLineOffset = do
1486+ ann <- annsA ps M. !? mkAnnKey (astA ps)
1487+ deltaPos <- fmap NE. head . NE. nonEmpty . mapMaybe filterWhere $ annsDP ann
1488+ pure $ deltaRow deltaPos
1489+
1490+ -- Before ghc 9.2, DeltaPos doesn't take comment into acccount, so we don't need to sum line offset of comments.
1491+ filterWhere :: (KeywordId , DeltaPos ) -> Maybe DeltaPos
1492+ filterWhere (keywordId, deltaPos) =
1493+ if keywordId == G AnnWhere then Just deltaPos else Nothing
1494+ #endif
14041495
1405- findPositionFromImportsOrModuleDecl :: HasSrcSpan a => t -> (t -> a ) -> Bool -> Maybe ((Int , Int ), Int )
1406- findPositionFromImportsOrModuleDecl hsField f hasImports = case getLoc (f hsField) of
1496+ findPositionFromImports :: HasSrcSpan a => t -> (t -> a ) -> Maybe ((Int , Int ), Int )
1497+ findPositionFromImports hsField f = case getLoc (f hsField) of
14071498 RealSrcSpan s _ ->
14081499 let col = calcCol s
14091500 in Just ((srcLocLine (realSrcSpanEnd s), col), col)
14101501 _ -> Nothing
1411- where calcCol s = if hasImports then srcLocCol (realSrcSpanStart s) - 1 else 0
1502+ where calcCol s = srcLocCol (realSrcSpanStart s) - 1
14121503
14131504-- | Find the position one after the last file-header pragma
14141505-- Defaults to zero if there are no pragmas in file
1415- findNextPragmaPosition :: T. Text -> Maybe (( Int , Int ), Int )
1416- findNextPragmaPosition contents = Just (( lineNumber, 0 ), 0 )
1506+ findNextPragmaPosition :: T. Text -> Int
1507+ findNextPragmaPosition contents = lineNumber
14171508 where
14181509 lineNumber = afterLangPragma . afterOptsGhc $ afterShebang
14191510 afterLangPragma = afterPragma " LANGUAGE" contents'
0 commit comments