Skip to content

Commit df51305

Browse files
authored
Package ghcide code actions (#1512)
* Package ghcide code actions * HLint * Expand and remove TH, Remove the existential type * Support specifying code action kinds * Simplify * Optimize instances
1 parent 0f3eeac commit df51305

File tree

3 files changed

+348
-83
lines changed

3 files changed

+348
-83
lines changed

ghcide/ghcide.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ library
190190
Development.IDE.GHC.Warnings
191191
Development.IDE.LSP.Notifications
192192
Development.IDE.Plugin.CodeAction.PositionIndexed
193+
Development.IDE.Plugin.CodeAction.Args
193194
Development.IDE.Plugin.Completions.Logic
194195
Development.IDE.Session.VersionCheck
195196
Development.IDE.Types.Action

ghcide/src/Development/IDE/Plugin/CodeAction.hs

Lines changed: 55 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,10 @@
33

44
{-# LANGUAGE CPP #-}
55
{-# LANGUAGE DuplicateRecordFields #-}
6-
{-# LANGUAGE GADTs #-}
7-
{-# LANGUAGE RankNTypes #-}
86
#include "ghc-api-version.h"
97

108
-- | Go to the definition of a variable.
9+
1110
module Development.IDE.Plugin.CodeAction
1211
( descriptor
1312

@@ -20,7 +19,6 @@ import Control.Applicative ((<|>))
2019
import Control.Arrow (second,
2120
(>>>))
2221
import Control.Concurrent.Extra (readVar)
23-
import Control.Lens (alaf)
2422
import Control.Monad (guard, join)
2523
import Control.Monad.IO.Class
2624
import Data.Char
@@ -34,7 +32,6 @@ import Data.List.NonEmpty (NonEmpty ((:
3432
import qualified Data.List.NonEmpty as NE
3533
import qualified Data.Map as M
3634
import Data.Maybe
37-
import Data.Monoid (Ap (..))
3835
import qualified Data.Rope.UTF16 as Rope
3936
import qualified Data.Set as S
4037
import qualified Data.Text as T
@@ -47,13 +44,12 @@ import Development.IDE.GHC.Error
4744
import Development.IDE.GHC.ExactPrint
4845
import Development.IDE.GHC.Util (prettyPrint,
4946
printRdrName)
47+
import Development.IDE.Plugin.CodeAction.Args
5048
import Development.IDE.Plugin.CodeAction.ExactPrint
5149
import Development.IDE.Plugin.CodeAction.PositionIndexed
5250
import Development.IDE.Plugin.TypeLenses (GetGlobalBindingTypeSigs (GetGlobalBindingTypeSigs),
53-
GlobalBindingTypeSigsResult,
5451
suggestSignature)
5552
import Development.IDE.Spans.Common
56-
import Development.IDE.Spans.LocalBindings (Bindings)
5753
import Development.IDE.Types.Exports
5854
import Development.IDE.Types.HscEnvEq
5955
import Development.IDE.Types.Location
@@ -116,68 +112,44 @@ codeAction state _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range Cod
116112
exportsMap = localExports <> pkgExports
117113
df = ms_hspp_opts . pm_mod_summary <$> parsedModule
118114
actions =
119-
[ mkCA title [x] edit
120-
| x <- xs, (title, tedit) <- suggestAction exportsMap ideOptions parsedModule text df annotatedPS tcM har bindings gblSigs x
115+
[ mkCA title kind isPreferred [x] edit
116+
| x <- xs, (title, kind, isPreferred, tedit) <- suggestAction $ CodeActionArgs exportsMap ideOptions parsedModule text df annotatedPS tcM har bindings gblSigs x
121117
, let edit = WorkspaceEdit (Just $ Map.singleton uri $ List tedit) Nothing
122118
]
123119
actions' = caRemoveRedundantImports parsedModule text diag xs uri
124120
<> actions
125121
<> caRemoveInvalidExports parsedModule text diag xs uri
126122
pure $ Right $ List actions'
127123

128-
mkCA :: T.Text -> [Diagnostic] -> WorkspaceEdit -> (Command |? CodeAction)
129-
mkCA title diags edit =
130-
InR $ CodeAction title (Just CodeActionQuickFix) (Just $ List diags) Nothing Nothing (Just edit) Nothing
131-
132-
rewrite ::
133-
Maybe DynFlags ->
134-
Maybe (Annotated ParsedSource) ->
135-
(DynFlags -> ParsedSource -> [(T.Text, [Rewrite])]) ->
136-
[(T.Text, [TextEdit])]
137-
rewrite (Just df) (Just ps) f
138-
| Right edit <- (traverse . traverse)
139-
(alaf Ap foldMap (rewriteToEdit df (annsA ps)))
140-
(f df $ astA ps) = edit
141-
rewrite _ _ _ = []
142-
143-
suggestAction
144-
:: ExportsMap
145-
-> IdeOptions
146-
-> Maybe ParsedModule
147-
-> Maybe T.Text
148-
-> Maybe DynFlags
149-
-> Maybe (Annotated ParsedSource)
150-
-> Maybe TcModuleResult
151-
-> Maybe HieAstResult
152-
-> Maybe Bindings
153-
-> Maybe GlobalBindingTypeSigsResult
154-
-> Diagnostic
155-
-> [(T.Text, [TextEdit])]
156-
suggestAction packageExports ideOptions parsedModule text df annSource tcM har bindings gblSigs diag =
157-
concat
158-
-- Order these suggestions by priority
159-
[ suggestSignature True gblSigs tcM bindings diag
160-
, rewrite df annSource $ \_ ps -> suggestExtendImport packageExports ps diag
161-
, rewrite df annSource $ \df ps ->
162-
suggestImportDisambiguation df text ps diag
163-
, rewrite df annSource $ \_ ps -> suggestNewOrExtendImportForClassMethod packageExports ps diag
164-
, suggestFillTypeWildcard diag
165-
, suggestFixConstructorImport text diag
166-
, suggestModuleTypo diag
167-
, suggestReplaceIdentifier text diag
168-
, removeRedundantConstraints text diag
169-
, suggestAddTypeAnnotationToSatisfyContraints text diag
170-
, rewrite df annSource $ \df ps -> suggestConstraint df ps diag
171-
, rewrite df annSource $ \_ ps -> suggestImplicitParameter ps diag
172-
, rewrite df annSource $ \_ ps -> suggestHideShadow ps tcM har diag
173-
] ++ concat
174-
[ suggestNewDefinition ideOptions pm text diag
175-
++ suggestNewImport packageExports pm diag
176-
++ suggestDeleteUnusedBinding pm text diag
177-
++ suggestExportUnusedTopBinding text pm diag
178-
| Just pm <- [parsedModule]
179-
] ++
180-
suggestFillHole diag -- Lowest priority
124+
mkCA :: T.Text -> Maybe CodeActionKind -> Maybe Bool -> [Diagnostic] -> WorkspaceEdit -> (Command |? CodeAction)
125+
mkCA title kind isPreferred diags edit =
126+
InR $ CodeAction title kind (Just $ List diags) isPreferred Nothing (Just edit) Nothing
127+
128+
suggestAction :: CodeActionArgs -> GhcideCodeActions
129+
suggestAction caa =
130+
concat -- Order these suggestions by priority
131+
[ wrap $ suggestSignature True
132+
, wrap suggestExtendImport
133+
, wrap suggestImportDisambiguation
134+
, wrap suggestNewOrExtendImportForClassMethod
135+
, wrap suggestFillTypeWildcard
136+
, wrap suggestFixConstructorImport
137+
, wrap suggestModuleTypo
138+
, wrap suggestReplaceIdentifier
139+
, wrap removeRedundantConstraints
140+
, wrap suggestAddTypeAnnotationToSatisfyContraints
141+
, wrap suggestConstraint
142+
, wrap suggestImplicitParameter
143+
, wrap suggestHideShadow
144+
, wrap suggestNewDefinition
145+
, wrap suggestNewImport
146+
, wrap suggestDeleteUnusedBinding
147+
, wrap suggestExportUnusedTopBinding
148+
, wrap suggestFillHole -- Lowest priority
149+
]
150+
where
151+
wrap :: ToCodeAction a => a -> GhcideCodeActions
152+
wrap = toCodeAction caa
181153

182154
findSigOfDecl :: (IdP p -> Bool) -> [LHsDecl p] -> Maybe (Sig p)
183155
findSigOfDecl pred decls =
@@ -304,7 +276,7 @@ caRemoveRedundantImports m contents digs ctxDigs uri
304276
= caRemoveCtx ++ [caRemoveAll]
305277
| otherwise = []
306278
where
307-
removeSingle title tedit diagnostic = mkCA title [diagnostic] WorkspaceEdit{..} where
279+
removeSingle title tedit diagnostic = mkCA title (Just CodeActionQuickFix) Nothing [diagnostic] WorkspaceEdit{..} where
308280
_changes = Just $ Map.singleton uri $ List tedit
309281
_documentChanges = Nothing
310282
removeAll tedit = InR $ CodeAction{..} where
@@ -504,7 +476,7 @@ data ExportsAs = ExportName | ExportPattern | ExportAll
504476
getLocatedRange :: Located a -> Maybe Range
505477
getLocatedRange = srcSpanToRange . getLoc
506478

507-
suggestExportUnusedTopBinding :: Maybe T.Text -> ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])]
479+
suggestExportUnusedTopBinding :: Maybe T.Text -> ParsedModule -> Diagnostic -> [(T.Text, TextEdit)]
508480
suggestExportUnusedTopBinding srcOpt ParsedModule{pm_parsed_source = L _ HsModule{..}} Diagnostic{..}
509481
-- Foo.hs:4:1: warning: [-Wunused-top-binds] Defined but not used: ‘f’
510482
-- Foo.hs:5:1: warning: [-Wunused-top-binds] Defined but not used: type constructor or class ‘F’
@@ -522,7 +494,7 @@ suggestExportUnusedTopBinding srcOpt ParsedModule{pm_parsed_source = L _ HsModul
522494
, Just needComma <- needsComma source <$> hsmodExports
523495
, let exportName = (if needComma then "," else "") <> printExport exportType name
524496
insertPos = pos {_character = pred $ _character pos}
525-
= [("Export ‘" <> name <> "", [TextEdit (Range insertPos insertPos) exportName])]
497+
= [("Export ‘" <> name <> "", TextEdit (Range insertPos insertPos) exportName)]
526498
| otherwise = []
527499
where
528500
-- we get the last export and the closing bracket and check for comma in that range
@@ -669,30 +641,30 @@ newDefinitionAction IdeOptions{..} parsedModule Range{_start} name typ
669641
ParsedModule{pm_parsed_source = L _ HsModule{hsmodDecls}} = parsedModule
670642

671643

672-
suggestFillTypeWildcard :: Diagnostic -> [(T.Text, [TextEdit])]
644+
suggestFillTypeWildcard :: Diagnostic -> [(T.Text, TextEdit)]
673645
suggestFillTypeWildcard Diagnostic{_range=_range,..}
674646
-- Foo.hs:3:8: error:
675647
-- * Found type wildcard `_' standing for `p -> p1 -> p'
676648

677649
| "Found type wildcard" `T.isInfixOf` _message
678650
, " standing for " `T.isInfixOf` _message
679651
, typeSignature <- extractWildCardTypeSignature _message
680-
= [("Use type signature: ‘" <> typeSignature <> "", [TextEdit _range typeSignature])]
652+
= [("Use type signature: ‘" <> typeSignature <> "", TextEdit _range typeSignature)]
681653
| otherwise = []
682654

683-
suggestModuleTypo :: Diagnostic -> [(T.Text, [TextEdit])]
655+
suggestModuleTypo :: Diagnostic -> [(T.Text, TextEdit)]
684656
suggestModuleTypo Diagnostic{_range=_range,..}
685657
-- src/Development/IDE/Core/Compile.hs:58:1: error:
686658
-- Could not find module ‘Data.Cha’
687659
-- Perhaps you meant Data.Char (from base-4.12.0.0)
688660
| "Could not find module" `T.isInfixOf` _message
689661
, "Perhaps you meant" `T.isInfixOf` _message = let
690662
findSuggestedModules = map (head . T.words) . drop 2 . T.lines
691-
proposeModule mod = ("replace with " <> mod, [TextEdit _range mod])
663+
proposeModule mod = ("replace with " <> mod, TextEdit _range mod)
692664
in map proposeModule $ nubOrd $ findSuggestedModules _message
693665
| otherwise = []
694666

695-
suggestFillHole :: Diagnostic -> [(T.Text, [TextEdit])]
667+
suggestFillHole :: Diagnostic -> [(T.Text, TextEdit)]
696668
suggestFillHole Diagnostic{_range=_range,..}
697669
| Just holeName <- extractHoleName _message
698670
, (holeFits, refFits) <- processHoleSuggestions (T.lines _message)
@@ -703,7 +675,7 @@ suggestFillHole Diagnostic{_range=_range,..}
703675
extractHoleName = fmap head . flip matchRegexUnifySpaces "Found hole: ([^ ]*)"
704676
proposeHoleFit holeName parenthise name =
705677
( "replace " <> holeName <> " with " <> name
706-
, [TextEdit _range $ if parenthise then parens name else name])
678+
, TextEdit _range $ if parenthise then parens name else name)
707679
parens x = "(" <> x <> ")"
708680

709681
processHoleSuggestions :: [T.Text] -> ([T.Text], [T.Text])
@@ -766,7 +738,7 @@ getIndentedGroupsBy pred inp = case dropWhile (not.pred) inp of
766738
indentation :: T.Text -> Int
767739
indentation = T.length . T.takeWhile isSpace
768740

769-
suggestExtendImport :: ExportsMap -> ParsedSource -> Diagnostic -> [(T.Text, [Rewrite])]
741+
suggestExtendImport :: ExportsMap -> ParsedSource -> Diagnostic -> [(T.Text, Rewrite)]
770742
suggestExtendImport exportsMap (L _ HsModule {hsmodImports}) Diagnostic{_range=_range,..}
771743
| Just [binding, mod, srcspan] <-
772744
matchRegexUnifySpaces _message
@@ -785,7 +757,7 @@ suggestExtendImport exportsMap (L _ HsModule {hsmodImports}) Diagnostic{_range=_
785757
Just decl <- findImportDeclByRange decls range,
786758
Just ident <- lookupExportMap binding mod
787759
= [ ( "Add " <> renderImportStyle importStyle <> " to the import list of " <> mod
788-
, [uncurry extendImport (unImportStyle importStyle) decl]
760+
, uncurry extendImport (unImportStyle importStyle) decl
789761
)
790762
| importStyle <- NE.toList $ importStyles ident
791763
]
@@ -955,8 +927,8 @@ disambiguateSymbol pm Diagnostic {..} (T.unpack -> symbol) = \case
955927
findImportDeclByRange :: [LImportDecl GhcPs] -> Range -> Maybe (LImportDecl GhcPs)
956928
findImportDeclByRange xs range = find (\(L l _)-> srcSpanToRange l == Just range) xs
957929

958-
suggestFixConstructorImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
959-
suggestFixConstructorImport _ Diagnostic{_range=_range,..}
930+
suggestFixConstructorImport :: Diagnostic -> [(T.Text, TextEdit)]
931+
suggestFixConstructorImport Diagnostic{_range=_range,..}
960932
-- ‘Success’ is a data constructor of ‘Result’
961933
-- To import it use
962934
-- import Data.Aeson.Types( Result( Success ) )
@@ -966,16 +938,16 @@ suggestFixConstructorImport _ Diagnostic{_range=_range,..}
966938
matchRegexUnifySpaces _message
967939
"‘([^’]*)’ is a data constructor of ‘([^’]*)’ To import it use"
968940
= let fixedImport = typ <> "(" <> constructor <> ")"
969-
in [("Fix import of " <> fixedImport, [TextEdit _range fixedImport])]
941+
in [("Fix import of " <> fixedImport, TextEdit _range fixedImport)]
970942
| otherwise = []
971943
-- | Suggests a constraint for a declaration for which a constraint is missing.
972-
suggestConstraint :: DynFlags -> ParsedSource -> Diagnostic -> [(T.Text, [Rewrite])]
944+
suggestConstraint :: DynFlags -> ParsedSource -> Diagnostic -> [(T.Text, Rewrite)]
973945
suggestConstraint df parsedModule diag@Diagnostic {..}
974946
| Just missingConstraint <- findMissingConstraint _message
975947
= let codeAction = if _message =~ ("the type signature for:" :: String)
976948
then suggestFunctionConstraint df parsedModule
977949
else suggestInstanceConstraint df parsedModule
978-
in map (second (:[])) $ codeAction diag missingConstraint
950+
in codeAction diag missingConstraint
979951
| otherwise = []
980952
where
981953
findMissingConstraint :: T.Text -> Maybe T.Text
@@ -1031,14 +1003,14 @@ suggestInstanceConstraint df (L _ HsModule {hsmodDecls}) Diagnostic {..} missing
10311003
suggestImplicitParameter ::
10321004
ParsedSource ->
10331005
Diagnostic ->
1034-
[(T.Text, [Rewrite])]
1006+
[(T.Text, Rewrite)]
10351007
suggestImplicitParameter (L _ HsModule {hsmodDecls}) Diagnostic {_message, _range}
10361008
| Just [implicitT] <- matchRegexUnifySpaces _message "Unbound implicit parameter \\(([^:]+::.+)\\) arising",
10371009
Just (L _ (ValD _ FunBind {fun_id = L _ funId})) <- findDeclContainingLoc (_start _range) hsmodDecls,
10381010
Just (TypeSig _ _ HsWC {hswc_body = HsIB {hsib_body}}) <- findSigOfDecl (== funId) hsmodDecls
10391011
=
10401012
[( "Add " <> implicitT <> " to the context of " <> T.pack (printRdrName funId)
1041-
, [appendConstraint (T.unpack implicitT) hsib_body])]
1013+
, appendConstraint (T.unpack implicitT) hsib_body)]
10421014
| otherwise = []
10431015

10441016
findTypeSignatureName :: T.Text -> Maybe T.Text
@@ -1086,7 +1058,7 @@ suggestFunctionConstraint df (L _ HsModule {hsmodDecls}) Diagnostic {..} missing
10861058
<> "` to the context of the type signature for `" <> typeSignatureName <> "`"
10871059

10881060
-- | Suggests the removal of a redundant constraint for a type signature.
1089-
removeRedundantConstraints :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
1061+
removeRedundantConstraints :: Maybe T.Text -> Diagnostic -> [(T.Text, TextEdit)]
10901062
removeRedundantConstraints mContents Diagnostic{..}
10911063
-- • Redundant constraint: Eq a
10921064
-- • In the type signature for:
@@ -1108,7 +1080,7 @@ removeRedundantConstraints mContents Diagnostic{..}
11081080
endOfConstraint = Position typeSignatureLine $
11091081
typeSignatureFirstChar + T.length (constraints <> " => ")
11101082
range = Range startOfConstraint endOfConstraint
1111-
in [(actionTitle redundantConstraintList typeSignatureName, [TextEdit range newConstraints])]
1083+
in [(actionTitle redundantConstraintList typeSignatureName, TextEdit range newConstraints)]
11121084
| otherwise = []
11131085
where
11141086
parseConstraints :: T.Text -> [T.Text]
@@ -1197,7 +1169,7 @@ suggestNewOrExtendImportForClassMethod packageExportsMap ps Diagnostic {_message
11971169
]
11981170
<> maybeToList (("Import " <> moduleNameText,) <$> fmap pure (newImportAll (T.unpack moduleNameText) ps))
11991171

1200-
suggestNewImport :: ExportsMap -> ParsedModule -> Diagnostic -> [(T.Text, [TextEdit])]
1172+
suggestNewImport :: ExportsMap -> ParsedModule -> Diagnostic -> [(T.Text, TextEdit)]
12011173
suggestNewImport packageExportsMap ParsedModule {pm_parsed_source = L _ HsModule {..}} Diagnostic{_message}
12021174
| msg <- unifySpaces _message
12031175
, Just thingMissing <- extractNotInScopeName msg
@@ -1217,7 +1189,7 @@ suggestNewImport packageExportsMap ParsedModule {pm_parsed_source = L _ HsModule
12171189
, insertPos <- Position insertLine 0
12181190
, extendImportSuggestions <- matchRegexUnifySpaces msg
12191191
"Perhaps you want to add ‘[^’]*’ to the import list in the import of ‘([^’]*)’"
1220-
= [(imp, [TextEdit (Range insertPos insertPos) (imp <> "\n")])
1192+
= [(imp, TextEdit (Range insertPos insertPos) (imp <> "\n"))
12211193
| imp <- sort $ constructNewImportSuggestions packageExportsMap (qual <|> qual', thingMissing) extendImportSuggestions
12221194
]
12231195
suggestNewImport _ _ _ = []

0 commit comments

Comments
 (0)