5
5
{-# LANGUAGE DuplicateRecordFields #-}
6
6
{-# LANGUAGE GADTs #-}
7
7
8
- -- | Go to the definition of a variable.
9
-
10
8
module Development.IDE.Plugin.CodeAction
11
9
(
12
10
iePluginDescriptor ,
@@ -21,7 +19,8 @@ module Development.IDE.Plugin.CodeAction
21
19
22
20
import Control.Applicative ((<|>) )
23
21
import Control.Arrow (second ,
24
- (>>>) )
22
+ (>>>) ,
23
+ (&&&) )
25
24
import Control.Concurrent.STM.Stats (atomically )
26
25
import Control.Monad (guard , join ,
27
26
msum )
@@ -37,6 +36,7 @@ import Data.List.NonEmpty (NonEmpty ((:
37
36
import qualified Data.List.NonEmpty as NE
38
37
import qualified Data.Map as M
39
38
import Data.Maybe
39
+ import Data.Ord (comparing )
40
40
import qualified Data.Rope.UTF16 as Rope
41
41
import qualified Data.Set as S
42
42
import qualified Data.Text as T
@@ -107,7 +107,7 @@ iePluginDescriptor :: PluginId -> PluginDescriptor IdeState
107
107
iePluginDescriptor plId =
108
108
let old =
109
109
mkGhcideCAsPlugin [
110
- wrap suggestExtendImport
110
+ wrap suggestExtendImport
111
111
, wrap suggestImportDisambiguation
112
112
, wrap suggestNewOrExtendImportForClassMethod
113
113
, wrap suggestNewImport
@@ -227,8 +227,10 @@ findInstanceHead df instanceHead decls =
227
227
228
228
#if MIN_VERSION_ghc(9,2,0)
229
229
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 )
230
232
#else
231
- -- TODO populate this type signature for GHC versions <9.2
233
+ -- TODO populate this type signature for GHC versions <8.10
232
234
#endif
233
235
findDeclContainingLoc loc = find (\ (L l _) -> loc `isInsideSrcSpan` locA l)
234
236
@@ -549,48 +551,73 @@ suggestDeleteUnusedBinding
549
551
data ExportsAs = ExportName | ExportPattern | ExportFamily | ExportAll
550
552
deriving (Eq )
551
553
552
- getLocatedRange :: Located a -> Maybe Range
554
+ getLocatedRange :: HasSrcSpan a => a -> Maybe Range
553
555
getLocatedRange = srcSpanToRange . getLoc
554
556
555
- suggestExportUnusedTopBinding :: Maybe T. Text -> ParsedModule -> Diagnostic -> [ (T. Text , TextEdit )]
557
+ suggestExportUnusedTopBinding :: Maybe T. Text -> ParsedModule -> Diagnostic -> Maybe (T. Text , TextEdit )
556
558
suggestExportUnusedTopBinding srcOpt ParsedModule {pm_parsed_source = L _ HsModule {.. }} Diagnostic {.. }
557
559
-- Foo.hs:4:1: warning: [-Wunused-top-binds] Defined but not used: ‘f’
558
560
-- Foo.hs:5:1: warning: [-Wunused-top-binds] Defined but not used: type constructor or class ‘F’
559
561
-- Foo.hs:6:1: warning: [-Wunused-top-binds] Defined but not used: data constructor ‘Bar’
560
562
| Just source <- srcOpt
561
- , Just [name] <- matchRegexUnifySpaces _message " .*Defined but not used: ‘([^ ]+)’"
562
- <|> matchRegexUnifySpaces _message " .*Defined but not used: type constructor or class ‘([^ ]+)’"
563
- <|> matchRegexUnifySpaces _message " .*Defined but not used: data constructor ‘([^ ]+)’"
564
- , Just (exportType, _) <- find (matchWithDiagnostic _range . snd )
565
- . mapMaybe
566
- (\ (L (locA -> l) b) -> if maybe False isTopLevel $ srcSpanToRange l
567
- then exportsAs b else Nothing )
568
- $ hsmodDecls
569
- , Just pos <- (fmap _end . getLocatedRange) . reLoc =<< hsmodExports
570
- , Just needComma <- needsComma source <$> fmap reLoc hsmodExports
571
- , let exportName = (if needComma then " , " else " " ) <> printExport exportType name
572
- insertPos = pos {_character = pred $ _character pos}
573
- = [(" Export ‘" <> name <> " ’" , TextEdit (Range insertPos insertPos) exportName)]
574
- | otherwise = []
563
+ , Just [_, name] <-
564
+ matchRegexUnifySpaces
565
+ _message
566
+ " .*Defined but not used: (type constructor or class |data constructor )?‘([^ ]+)’"
567
+ , Just (exportType, _) <-
568
+ find (matchWithDiagnostic _range . snd )
569
+ . mapMaybe (\ (L l b) -> if isTopLevel (locA l) then exportsAs b else Nothing )
570
+ $ hsmodDecls
571
+ , Just exports <- fmap (fmap reLoc) . reLoc <$> hsmodExports
572
+ , Just exportsEndPos <- _end <$> getLocatedRange exports
573
+ , let name' = printExport exportType name
574
+ sep = exportSep source $ map getLocatedRange <$> exports
575
+ exportName = case sep of
576
+ Nothing -> (if needsComma source exports then " , " else " " ) <> name'
577
+ Just s -> s <> name'
578
+ exportsEndPos' = exportsEndPos { _character = pred $ _character exportsEndPos }
579
+ insertPos = fromMaybe exportsEndPos' $ case (sep, unLoc exports) of
580
+ (Just _, exports'@ (_: _)) -> fmap _end . getLocatedRange $ last exports'
581
+ _ -> Nothing
582
+ = Just (" Export ‘" <> name <> " ’" , TextEdit (Range insertPos insertPos) exportName)
583
+ | otherwise = Nothing
575
584
where
576
- -- we get the last export and the closing bracket and check for comma in that range
577
- needsComma :: T. Text -> Located [LIE GhcPs ] -> Bool
585
+ exportSep :: T. Text -> Located [Maybe Range ] -> Maybe T. Text
586
+ exportSep src (L (RealSrcSpan _ _) xs@ (_ : tl@ (_ : _))) =
587
+ case mapMaybe (\ (e, s) -> (,) <$> e <*> s) $ zip (fmap _end <$> xs) (fmap _start <$> tl) of
588
+ [] -> Nothing
589
+ bounds -> Just smallestSep
590
+ where
591
+ smallestSep
592
+ = snd
593
+ $ minimumBy (comparing fst )
594
+ $ map (T. length &&& id )
595
+ $ nubOrd
596
+ $ map (\ (prevEnd, nextStart) -> textInRange (Range prevEnd nextStart) src) bounds
597
+ exportSep _ _ = Nothing
598
+
599
+ -- We get the last export and the closing bracket and check for comma in that range.
600
+ needsComma :: T. Text -> Located [Located (IE GhcPs )] -> Bool
578
601
needsComma _ (L _ [] ) = False
579
602
needsComma source (L (RealSrcSpan l _) exports) =
580
- let closeParan = _end $ realSrcSpanToRange l
581
- lastExport = fmap _end . getLocatedRange $ last $ fmap reLoc exports
582
- in case lastExport of
583
- Just lastExport -> not $ T. isInfixOf " ," $ textInRange (Range lastExport closeParan) source
603
+ let closeParen = _end $ realSrcSpanToRange l
604
+ lastExport = fmap _end . getLocatedRange $ last exports
605
+ in
606
+ case lastExport of
607
+ Just lastExport ->
608
+ not $ T. any (== ' ,' ) $ textInRange (Range lastExport closeParen) source
584
609
_ -> False
585
610
needsComma _ _ = False
586
611
587
- opLetter :: String
612
+ opLetter :: T. Text
588
613
opLetter = " :!#$%&*+./<=>?@\\ ^|-~"
589
614
590
615
parenthesizeIfNeeds :: Bool -> T. Text -> T. Text
591
616
parenthesizeIfNeeds needsTypeKeyword x
592
- | T. head x `elem` opLetter = (if needsTypeKeyword then " type " else " " ) <> " (" <> x <> " )"
617
+ | T. any (c == ) opLetter = (if needsTypeKeyword then " type " else " " ) <> " (" <> x <> " )"
593
618
| otherwise = x
619
+ where
620
+ c = T. head x
594
621
595
622
matchWithDiagnostic :: Range -> Located (IdP GhcPs ) -> Bool
596
623
matchWithDiagnostic Range {_start= l,_end= r} x =
@@ -603,8 +630,8 @@ suggestExportUnusedTopBinding srcOpt ParsedModule{pm_parsed_source = L _ HsModul
603
630
printExport ExportFamily x = parenthesizeIfNeeds True x
604
631
printExport ExportAll x = parenthesizeIfNeeds True x <> " (..)"
605
632
606
- isTopLevel :: Range -> Bool
607
- isTopLevel l = (_character . _start) l == 0
633
+ isTopLevel :: SrcSpan -> Bool
634
+ isTopLevel span = fmap (_character . _start) (srcSpanToRange span ) == Just 0
608
635
609
636
exportsAs :: HsDecl GhcPs -> Maybe (ExportsAs , Located (IdP GhcPs ))
610
637
exportsAs (ValD _ FunBind {fun_id}) = Just (ExportName , reLoc fun_id)
@@ -1218,7 +1245,7 @@ removeRedundantConstraints df (L _ HsModule {hsmodDecls}) Diagnostic{..}
1218
1245
= [(actionTitle redundantConstraintList typeSignatureName, rewrite)]
1219
1246
| otherwise = []
1220
1247
where
1221
- toRemove df list a = showSDoc df (ppr a) `elem` ( T. unpack <$> list)
1248
+ toRemove df list a = T. pack ( showSDoc df (ppr a)) `elem` list
1222
1249
1223
1250
parseConstraints :: T. Text -> [T. Text ]
1224
1251
parseConstraints t = t
0 commit comments