@@ -19,11 +19,10 @@ module Development.IDE.Plugin.CodeAction
19
19
20
20
import Control.Applicative ((<|>) )
21
21
import Control.Arrow (second ,
22
- (>>> ) ,
23
- (&&& ) )
22
+ (&&& ) ,
23
+ (>>> ) )
24
24
import Control.Concurrent.STM.Stats (atomically )
25
- import Control.Monad (guard , join ,
26
- msum )
25
+ import Control.Monad (guard , join )
27
26
import Control.Monad.IO.Class
28
27
import Data.Char
29
28
import qualified Data.DList as DL
@@ -34,7 +33,7 @@ import qualified Data.HashSet as Set
34
33
import Data.List.Extra
35
34
import Data.List.NonEmpty (NonEmpty ((:|) ))
36
35
import qualified Data.List.NonEmpty as NE
37
- import qualified Data.Map as M
36
+ import qualified Data.Map.Strict as M
38
37
import Data.Maybe
39
38
import Data.Ord (comparing )
40
39
import qualified Data.Rope.UTF16 as Rope
@@ -47,7 +46,6 @@ import Development.IDE.Core.Service
47
46
import Development.IDE.GHC.Compat
48
47
import Development.IDE.GHC.Compat.Util
49
48
import Development.IDE.GHC.Error
50
- import Development.IDE.GHC.ExactPrint
51
49
import Development.IDE.GHC.Util (printOutputable ,
52
50
printRdrName ,
53
51
traceAst )
@@ -80,6 +78,25 @@ import Language.LSP.Types (CodeAction (
80
78
import Language.LSP.VFS
81
79
import Text.Regex.TDFA (mrAfter ,
82
80
(=~) , (=~~) )
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
83
100
84
101
-------------------------------------------------------------------------------------------------
85
102
@@ -227,10 +244,8 @@ findInstanceHead df instanceHead decls =
227
244
228
245
#if MIN_VERSION_ghc(9,2,0)
229
246
findDeclContainingLoc :: 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 )
232
247
#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 )
234
249
#endif
235
250
findDeclContainingLoc loc = find (\ (L l _) -> loc `isInsideSrcSpan` locA l)
236
251
@@ -243,8 +258,8 @@ findDeclContainingLoc loc = find (\(L l _) -> loc `isInsideSrcSpan` locA l)
243
258
-- imported from ‘Data.ByteString’ at B.hs:6:1-22
244
259
-- imported from ‘Data.ByteString.Lazy’ at B.hs:8:1-27
245
260
-- 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}
248
263
| Just [identifier, modName, s] <-
249
264
matchRegexUnifySpaces
250
265
_message
@@ -261,6 +276,8 @@ suggestHideShadow ps@(L _ HsModule {hsmodImports}) fileContents mTcM mHar Diagno
261
276
result <> [hideAll]
262
277
| otherwise = []
263
278
where
279
+ L _ HsModule {hsmodImports} = astA ps
280
+
264
281
suggests identifier modName s
265
282
| Just tcM <- mTcM,
266
283
Just har <- mHar,
@@ -940,11 +957,11 @@ isPreludeImplicit = xopt Lang.ImplicitPrelude
940
957
suggestImportDisambiguation ::
941
958
DynFlags ->
942
959
Maybe T. Text ->
943
- ParsedSource ->
960
+ Annotated ParsedSource ->
944
961
T. Text ->
945
962
Diagnostic ->
946
963
[(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 {.. }
948
965
| Just [ambiguous] <-
949
966
matchRegexUnifySpaces
950
967
_message
@@ -956,6 +973,8 @@ suggestImportDisambiguation df (Just txt) ps@(L _ HsModule {hsmodImports}) fileC
956
973
suggestions ambiguous modules (isJust local)
957
974
| otherwise = []
958
975
where
976
+ L _ HsModule {hsmodImports} = astA ps
977
+
959
978
locDic =
960
979
fmap (NE. fromList . DL. toList) $
961
980
Map. fromListWith (<>) $
@@ -1048,21 +1067,21 @@ targetModuleName (ExistingImp _) =
1048
1067
error " Cannot happen!"
1049
1068
1050
1069
disambiguateSymbol ::
1051
- ParsedSource ->
1070
+ Annotated ParsedSource ->
1052
1071
T. Text ->
1053
1072
Diagnostic ->
1054
1073
T. Text ->
1055
1074
HidingMode ->
1056
1075
[Either TextEdit Rewrite ]
1057
- disambiguateSymbol pm fileContents Diagnostic {.. } (T. unpack -> symbol) = \ case
1076
+ disambiguateSymbol ps fileContents Diagnostic {.. } (T. unpack -> symbol) = \ case
1058
1077
(HideOthers hiddens0) ->
1059
1078
[ Right $ hideSymbol symbol idecl
1060
1079
| ExistingImp idecls <- hiddens0
1061
1080
, idecl <- NE. toList idecls
1062
1081
]
1063
1082
++ mconcat
1064
1083
[ 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
1066
1085
else Right . hideSymbol symbol <$> imps
1067
1086
| ImplicitPrelude imps <- hiddens0
1068
1087
]
@@ -1292,7 +1311,7 @@ removeRedundantConstraints df (L _ HsModule {hsmodDecls}) Diagnostic{..}
1292
1311
1293
1312
-------------------------------------------------------------------------------------------------
1294
1313
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 ])]
1296
1315
suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnostic {_message}
1297
1316
| Just [methodName, className] <-
1298
1317
matchRegexUnifySpaces
@@ -1306,7 +1325,7 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
1306
1325
where
1307
1326
suggest identInfo@ IdentInfo {moduleNameText}
1308
1327
| importStyle <- NE. toList $ importStyles identInfo,
1309
- mImportDecl <- findImportDeclByModuleName (hsmodImports $ unLoc ps) (T. unpack moduleNameText) =
1328
+ mImportDecl <- findImportDeclByModuleName (hsmodImports . unLoc . astA $ ps) (T. unpack moduleNameText) =
1310
1329
case mImportDecl of
1311
1330
-- extend
1312
1331
Just decl ->
@@ -1328,8 +1347,8 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps fileContents Diagnos
1328
1347
<> [(quickFixImportKind " new.all" , newImportAll moduleNameText)]
1329
1348
| otherwise -> []
1330
1349
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}
1333
1352
| msg <- unifySpaces _message
1334
1353
, Just thingMissing <- extractNotInScopeName msg
1335
1354
, qual <- extractQualifiedModuleName msg
@@ -1344,6 +1363,8 @@ suggestNewImport packageExportsMap ps@(L _ HsModule {..}) fileContents Diagnosti
1344
1363
= sortOn fst3 [(imp, kind, TextEdit range (imp <> " \n " <> T. replicate indent " " ))
1345
1364
| (kind, unNewImport -> imp) <- constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions
1346
1365
]
1366
+ where
1367
+ L _ HsModule {.. } = astA ps
1347
1368
suggestNewImport _ _ _ _ = []
1348
1369
1349
1370
constructNewImportSuggestions
@@ -1371,7 +1392,7 @@ constructNewImportSuggestions exportsMap (qual, thingMissing) notTheseModules =
1371
1392
newtype NewImport = NewImport { unNewImport :: T. Text}
1372
1393
deriving (Show , Eq , Ord )
1373
1394
1374
- newImportToEdit :: NewImport -> ParsedSource -> T. Text -> Maybe (T. Text , TextEdit )
1395
+ newImportToEdit :: NewImport -> Annotated ParsedSource -> T. Text -> Maybe (T. Text , TextEdit )
1375
1396
newImportToEdit (unNewImport -> imp) ps fileContents
1376
1397
| Just (range, indent) <- newImportInsertRange ps fileContents
1377
1398
= Just (imp, TextEdit range (imp <> " \n " <> T. replicate indent " " ))
@@ -1385,35 +1406,105 @@ newImportToEdit (unNewImport -> imp) ps fileContents
1385
1406
-- * If the file has neither existing imports nor a module declaration,
1386
1407
-- the import will be inserted at line zero if there are no pragmas,
1387
1408
-- * 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
1390
1411
| 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
1393
1415
, let insertPos = Position (fromIntegral l) (fromIntegral c)
1394
1416
= Just (Range insertPos insertPos, col)
1395
1417
| 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
1396
1429
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
1404
1495
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
1407
1498
RealSrcSpan s _ ->
1408
1499
let col = calcCol s
1409
1500
in Just ((srcLocLine (realSrcSpanEnd s), col), col)
1410
1501
_ -> Nothing
1411
- where calcCol s = if hasImports then srcLocCol (realSrcSpanStart s) - 1 else 0
1502
+ where calcCol s = srcLocCol (realSrcSpanStart s) - 1
1412
1503
1413
1504
-- | Find the position one after the last file-header pragma
1414
1505
-- 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
1417
1508
where
1418
1509
lineNumber = afterLangPragma . afterOptsGhc $ afterShebang
1419
1510
afterLangPragma = afterPragma " LANGUAGE" contents'
0 commit comments