@@ -16,6 +16,7 @@ import Development.IDE.GHC.Compat
1616import Development.IDE.Core.Rules
1717import Development.IDE.Core.RuleTypes
1818import Development.IDE.Core.Shake
19+import Development.IDE.GHC.Error
1920import Development.IDE.LSP.Server
2021import Development.IDE.Types.Location
2122import qualified Data.HashMap.Strict as Map
@@ -85,8 +86,22 @@ executeAddSignatureCommand _lsp _ideState ExecuteCommandParams{..}
8586 | otherwise
8687 = return (Null, Nothing)
8788
88−suggestAction :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
89−suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
89+suggestAction :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
90+suggestAction text diag = concat
91+ [ suggestAddExtension diag
92+ , suggestExtendImport text diag
93+ , suggestFillHole diag
94+ , suggestFillTypeWildcard diag
95+ , suggestFixConstructorImport text diag
96+ , suggestModuleTypo diag
97+ , suggestRemoveRedundantImport text diag
98+ , suggestReplaceIdentifier text diag
99+ , suggestSignature True diag
100+ ]
101+
102+
103+suggestRemoveRedundantImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
104+suggestRemoveRedundantImport contents Diagnostic{_range=_range@Range{..},..}
90105-- The qualified import of ‘many’ from module ‘Control.Applicative’ is redundant
91106 | Just [_, bindings] <- matchRegex _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"
92107 , Just c <- contents
@@ -100,7 +115,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
100115-- To import instances alone, use: import Data.List()
101116 | _message =~ ("The( qualified)? import of [^ ]* is redundant" :: String)
102117 = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]
118+ | otherwise = []
103119
120+suggestReplaceIdentifier :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
121+suggestReplaceIdentifier contents Diagnostic{_range=_range@Range{..},..}
104122-- File.hs:52:41: error:
105123-- * Variable not in scope:
106124-- suggestAcion :: Maybe T.Text -> Range -> Range
@@ -114,7 +132,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
114132-- Module ‘Data.Text’ does not export ‘isPrfixOf’.
115133 | renameSuggestions@(_:_) <- extractRenamableTerms _message
116134 = [ ("Replace with ‘" <> name <> "’", [mkRenameEdit contents _range name]) | name <- renameSuggestions ]
135+ | otherwise = []
117136
137+suggestFillTypeWildcard :: Diagnostic -> [(T.Text, [TextEdit])]
138+suggestFillTypeWildcard Diagnostic{_range=_range@Range{..},..}
118139-- Foo.hs:3:8: error:
119140-- * Found type wildcard `_' standing for `p -> p1 -> p'
120141
@@ -122,7 +143,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
122143 , " standing for " `T.isInfixOf` _message
123144 , typeSignature <- extractWildCardTypeSignature _message
124145 = [("Use type signature: ‘" <> typeSignature <> "’", [TextEdit _range typeSignature])]
146+ | otherwise = []
125147
148+suggestAddExtension :: Diagnostic -> [(T.Text, [TextEdit])]
149+suggestAddExtension Diagnostic{_range=_range@Range{..},..}
126150-- File.hs:22:8: error:
127151-- Illegal lambda-case (use -XLambdaCase)
128152-- File.hs:22:6: error:
@@ -145,7 +169,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
145169-- In the instance declaration for `Unit (m a)'
146170 | exts@(_:_) <- filter (`Set.member` ghcExtensions) $ T.split (not . isAlpha) $ T.replace "-X" "" _message
147171 = [("Add " <> x <> " extension", [TextEdit (Range (Position 0 0) (Position 0 0)) $ "{-# LANGUAGE " <> x <> " #-}\n"]) | x <- exts]
172+ | otherwise = []
148173
174+suggestModuleTypo :: Diagnostic -> [(T.Text, [TextEdit])]
175+suggestModuleTypo Diagnostic{_range=_range@Range{..},..}
149176-- src/Development/IDE/Core/Compile.hs:58:1: error:
150177-- Could not find module ‘Data.Cha’
151178-- Perhaps you meant Data.Char (from base-4.12.0.0)
@@ -154,7 +181,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
154181 findSuggestedModules = map (head . T.words) . drop 2 . T.lines
155182 proposeModule mod = ("replace with " <> mod, [TextEdit _range mod])
156183 in map proposeModule $ nubOrd $ findSuggestedModules _message
184+ | otherwise = []
157185
186+suggestFillHole :: Diagnostic -> [(T.Text, [TextEdit])]
187+suggestFillHole Diagnostic{_range=_range@Range{..},..}
158188-- ...Development/IDE/LSP/CodeAction.hs:103:9: warning:
159189-- * Found hole: _ :: Int -> String
160190-- * In the expression: _
@@ -187,9 +217,36 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
187217 extractFitNames = map (T.strip . head . T.splitOn " :: ")
188218 in map proposeHoleFit $ nubOrd $ findSuggestedHoleFits _message
189219
190− | tlb@[_] <- suggestSignature True diag = tlb
220+ | otherwise = []
191221
192−suggestAction _ _ = []
222+suggestExtendImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
223+suggestExtendImport contents Diagnostic{_range=_range,..}
224+ | Just [binding, mod, srcspan] <-
225+ matchRegex _message
226+ "Perhaps you want to add ‘([^’]*)’ to the import list in the import of ‘([^’]*)’ *\\((.*)\\).$"
227+ , Just c <- contents
228+ = let range = case [ x | (x,"") <- readSrcSpan (T.unpack srcspan)] of
229+ [s] -> let x = srcSpanToRange s
230+ in x{_end = (_end x){_character = succ (_character (_end x))}}
231+ _ -> error "bug in srcspan parser"
232+ importLine = textInRange range c
233+ in [("Add " <> binding <> " to the import list of " <> mod
234+ , [TextEdit range (addBindingToImportList binding importLine)])]
235+ | otherwise = []
236+
237+suggestFixConstructorImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
238+suggestFixConstructorImport _ Diagnostic{_range=_range,..}
239+ -- ‘Success’ is a data constructor of ‘Result’
240+ -- To import it use
241+ -- import Data.Aeson.Types( Result( Success ) )
242+ -- or
243+ -- import Data.Aeson.Types( Result(..) ) (lsp-ui)
244+ | Just [constructor, typ] <-
245+ matchRegex _message
246+ "‘([^’]*)’ is a data constructor of ‘([^’]*)’ To import it use"
247+ = let fixedImport = typ <> "(" <> constructor <> ")"
248+ in [("Fix import of " <> fixedImport, [TextEdit _range fixedImport])]
249+ | otherwise = []
193250
194251suggestSignature :: Bool -> Diagnostic -> [(T.Text, [TextEdit])]
195252suggestSignature isQuickFix Diagnostic{_range=_range@Range{..},..}
@@ -282,6 +339,7 @@ splitTextAtPosition (Position row col) x
282339 = (T.intercalate "\n" $ preRow ++ [preCol], T.intercalate "\n" $ postCol : postRow)
283340 | otherwise = (x, T.empty)
284341
342+-- | Returns [start .. end[
285343textInRange :: Range -> T.Text -> T.Text
286344textInRange (Range (Position startRow startCol) (Position endRow endCol)) text =
287345 case compare startRow endRow of
@@ -338,11 +396,27 @@ dropBindingsFromImportLine bindings_ importLine =
338396 joinCloseParens (x : rest) = x : joinCloseParens rest
339397 joinCloseParens [] = []
340398
399+-- | Extends an import list with a new binding.
400+-- Assumes an import statement of the form:
401+-- import (qualified) A (..) ..
402+-- Places the new binding first, preserving whitespace.
403+-- Copes with multi-line import lists
404+addBindingToImportList :: T.Text -> T.Text -> T.Text
405+addBindingToImportList binding importLine = case T.breakOn "(" importLine of
406+ (pre, T.uncons -> Just (_, rest)) ->
407+ case T.uncons (T.dropWhile isSpace rest) of
408+ Just (')', _) -> T.concat [pre, "(", binding, rest]
409+ _ -> T.concat [pre, "(", binding, ", ", rest]
410+ _ ->
411+ error
412+ $ "importLine does not have the expected structure: "
413+ <> T.unpack importLine
414+
341415-- | Returns Just (the submatches) for the first capture, or Nothing.
342416matchRegex :: T.Text -> T.Text -> Maybe [T.Text]
343−matchRegex message regex = case message =~~ regex of
344− Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings
345− Nothing -> Nothing
417+matchRegex message regex = case T.unwords (T.words message) =~~ regex of
418+ Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings
419+ Nothing -> Nothing
346420
347421setHandlersCodeAction :: PartialHandlers
348422setHandlersCodeAction = PartialHandlers $ \WithMessage{..} x -> return x{