@@ -16,6 +16,7 @@ import Development.IDE.GHC.Compat
16import Development.IDE.Core.Rules
16import Development.IDE.Core.Rules
17import Development.IDE.Core.RuleTypes
17import Development.IDE.Core.RuleTypes
18import Development.IDE.Core.Shake
18import Development.IDE.Core.Shake
19+import Development.IDE.GHC.Error
19import Development.IDE.LSP.Server
20import Development.IDE.LSP.Server
20import Development.IDE.Types.Location
21import Development.IDE.Types.Location
21import qualified Data.HashMap.Strict as Map
22import qualified Data.HashMap.Strict as Map
@@ -85,8 +86,22 @@ executeAddSignatureCommand _lsp _ideState ExecuteCommandParams{..}
85 | otherwise
86 | otherwise
86 = return (Null, Nothing)
87 = return (Null, Nothing)
88−suggestAction :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
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
103+suggestRemoveRedundantImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
89−suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
104+suggestRemoveRedundantImport contents Diagnostic{_range=_range@Range{..},..}
90-- The qualified import of ‘many’ from module ‘Control.Applicative’ is redundant
105-- The qualified import of ‘many’ from module ‘Control.Applicative’ is redundant
91 | Just [_, bindings] <- matchRegex _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"
106 | Just [_, bindings] <- matchRegex _message "The( qualified)? import of ‘([^’]*)’ from module [^ ]* is redundant"
92 , Just c <- contents
107 , Just c <- contents
@@ -100,7 +115,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
100-- To import instances alone, use: import Data.List()
115-- To import instances alone, use: import Data.List()
101 | _message =~ ("The( qualified)? import of [^ ]* is redundant" :: String)
116 | _message =~ ("The( qualified)? import of [^ ]* is redundant" :: String)
102 = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]
117 = [("Remove import", [TextEdit (extendToWholeLineIfPossible contents _range) ""])]
120+suggestReplaceIdentifier :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
121+suggestReplaceIdentifier contents Diagnostic{_range=_range@Range{..},..}
104-- File.hs:52:41: error:
122-- File.hs:52:41: error:
105-- * Variable not in scope:
123-- * Variable not in scope:
106-- suggestAcion :: Maybe T.Text -> Range -> Range
124-- suggestAcion :: Maybe T.Text -> Range -> Range
@@ -114,7 +132,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
114-- Module ‘Data.Text’ does not export ‘isPrfixOf’.
132-- Module ‘Data.Text’ does not export ‘isPrfixOf’.
115 | renameSuggestions@(_:_) <- extractRenamableTerms _message
133 | renameSuggestions@(_:_) <- extractRenamableTerms _message
116 = [ ("Replace with ‘" <> name <> "’", [mkRenameEdit contents _range name]) | name <- renameSuggestions ]
134 = [ ("Replace with ‘" <> name <> "’", [mkRenameEdit contents _range name]) | name <- renameSuggestions ]
137+suggestFillTypeWildcard :: Diagnostic -> [(T.Text, [TextEdit])]
138+suggestFillTypeWildcard Diagnostic{_range=_range@Range{..},..}
118-- Foo.hs:3:8: error:
139-- Foo.hs:3:8: error:
119-- * Found type wildcard `_' standing for `p -> p1 -> p'
140-- * Found type wildcard `_' standing for `p -> p1 -> p'
@@ -122,7 +143,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
122 , " standing for " `T.isInfixOf` _message
143 , " standing for " `T.isInfixOf` _message
123 , typeSignature <- extractWildCardTypeSignature _message
144 , typeSignature <- extractWildCardTypeSignature _message
124 = [("Use type signature: ‘" <> typeSignature <> "’", [TextEdit _range typeSignature])]
145 = [("Use type signature: ‘" <> typeSignature <> "’", [TextEdit _range typeSignature])]
148+suggestAddExtension :: Diagnostic -> [(T.Text, [TextEdit])]
149+suggestAddExtension Diagnostic{_range=_range@Range{..},..}
126-- File.hs:22:8: error:
150-- File.hs:22:8: error:
127-- Illegal lambda-case (use -XLambdaCase)
151-- Illegal lambda-case (use -XLambdaCase)
128-- File.hs:22:6: error:
152-- File.hs:22:6: error:
@@ -145,7 +169,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
145-- In the instance declaration for `Unit (m a)'
169-- In the instance declaration for `Unit (m a)'
146 | exts@(_:_) <- filter (`Set.member` ghcExtensions) $ T.split (not . isAlpha) $ T.replace "-X" "" _message
170 | exts@(_:_) <- filter (`Set.member` ghcExtensions) $ T.split (not . isAlpha) $ T.replace "-X" "" _message
147 = [("Add " <> x <> " extension", [TextEdit (Range (Position 0 0) (Position 0 0)) $ "{-# LANGUAGE " <> x <> " #-}\n"]) | x <- exts]
171 = [("Add " <> x <> " extension", [TextEdit (Range (Position 0 0) (Position 0 0)) $ "{-# LANGUAGE " <> x <> " #-}\n"]) | x <- exts]
174+suggestModuleTypo :: Diagnostic -> [(T.Text, [TextEdit])]
175+suggestModuleTypo Diagnostic{_range=_range@Range{..},..}
149-- src/Development/IDE/Core/Compile.hs:58:1: error:
176-- src/Development/IDE/Core/Compile.hs:58:1: error:
150-- Could not find module ‘Data.Cha’
177-- Could not find module ‘Data.Cha’
151-- Perhaps you meant Data.Char (from base-4.12.0.0)
178-- Perhaps you meant Data.Char (from base-4.12.0.0)
@@ -154,7 +181,10 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
154 findSuggestedModules = map (head . T.words) . drop 2 . T.lines
181 findSuggestedModules = map (head . T.words) . drop 2 . T.lines
155 proposeModule mod = ("replace with " <> mod, [TextEdit _range mod])
182 proposeModule mod = ("replace with " <> mod, [TextEdit _range mod])
156 in map proposeModule $ nubOrd $ findSuggestedModules _message
183 in map proposeModule $ nubOrd $ findSuggestedModules _message
186+suggestFillHole :: Diagnostic -> [(T.Text, [TextEdit])]
187+suggestFillHole Diagnostic{_range=_range@Range{..},..}
158-- ...Development/IDE/LSP/CodeAction.hs:103:9: warning:
188-- ...Development/IDE/LSP/CodeAction.hs:103:9: warning:
159-- * Found hole: _ :: Int -> String
189-- * Found hole: _ :: Int -> String
160-- * In the expression: _
190-- * In the expression: _
@@ -187,9 +217,36 @@ suggestAction contents diag@Diagnostic{_range=_range@Range{..},..}
187 extractFitNames = map (T.strip . head . T.splitOn " :: ")
217 extractFitNames = map (T.strip . head . T.splitOn " :: ")
188 in map proposeHoleFit $ nubOrd $ findSuggestedHoleFits _message
218 in map proposeHoleFit $ nubOrd $ findSuggestedHoleFits _message
190− | tlb@[_] <- suggestSignature True diag = tlb
220+ | otherwise = []
192−suggestAction _ _ = []
222+suggestExtendImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
223+suggestExtendImport contents Diagnostic{_range=_range,..}
224+ | Just [binding, mod, srcspan] <-
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)])]
237+suggestFixConstructorImport :: Maybe T.Text -> Diagnostic -> [(T.Text, [TextEdit])]
238+suggestFixConstructorImport _ Diagnostic{_range=_range,..}
239+ -- ‘Success’ is a data constructor of ‘Result’
241+ -- import Data.Aeson.Types( Result( Success ) )
243+ -- import Data.Aeson.Types( Result(..) ) (lsp-ui)
244+ | Just [constructor, typ] <-
246+ "‘([^’]*)’ is a data constructor of ‘([^’]*)’ To import it use"
247+ = let fixedImport = typ <> "(" <> constructor <> ")"
248+ in [("Fix import of " <> fixedImport, [TextEdit _range fixedImport])]
194suggestSignature :: Bool -> Diagnostic -> [(T.Text, [TextEdit])]
251suggestSignature :: Bool -> Diagnostic -> [(T.Text, [TextEdit])]
195suggestSignature isQuickFix Diagnostic{_range=_range@Range{..},..}
252suggestSignature isQuickFix Diagnostic{_range=_range@Range{..},..}
@@ -282,6 +339,7 @@ splitTextAtPosition (Position row col) x
282 = (T.intercalate "\n" $ preRow ++ [preCol], T.intercalate "\n" $ postCol : postRow)
339 = (T.intercalate "\n" $ preRow ++ [preCol], T.intercalate "\n" $ postCol : postRow)
283 | otherwise = (x, T.empty)
340 | otherwise = (x, T.empty)
342+-- | Returns [start .. end[
285textInRange :: Range -> T.Text -> T.Text
343textInRange :: Range -> T.Text -> T.Text
286textInRange (Range (Position startRow startCol) (Position endRow endCol)) text =
344textInRange (Range (Position startRow startCol) (Position endRow endCol)) text =
287 case compare startRow endRow of
345 case compare startRow endRow of
@@ -338,11 +396,27 @@ dropBindingsFromImportLine bindings_ importLine =
338 joinCloseParens (x : rest) = x : joinCloseParens rest
396 joinCloseParens (x : rest) = x : joinCloseParens rest
339 joinCloseParens [] = []
397 joinCloseParens [] = []
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]
412+ $ "importLine does not have the expected structure: "
413+ <> T.unpack importLine
341-- | Returns Just (the submatches) for the first capture, or Nothing.
415-- | Returns Just (the submatches) for the first capture, or Nothing.
342matchRegex :: T.Text -> T.Text -> Maybe [T.Text]
416matchRegex :: T.Text -> T.Text -> Maybe [T.Text]
343−matchRegex message regex = case message =~~ regex of
417+matchRegex message regex = case T.unwords (T.words message) =~~ regex of
344− Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings
418+ Just (_ :: T.Text, _ :: T.Text, _ :: T.Text, bindings) -> Just bindings
345− Nothing -> Nothing
419+ Nothing -> Nothing
347setHandlersCodeAction :: PartialHandlers
421setHandlersCodeAction :: PartialHandlers
348setHandlersCodeAction = PartialHandlers $ \WithMessage{..} x -> return x{
422setHandlersCodeAction = PartialHandlers $ \WithMessage{..} x -> return x{