11{-# LANGUAGE CPP #-}
22{-# LANGUAGE DataKinds #-}
33{-# LANGUAGE DuplicateRecordFields #-}
4+ {-# LANGUAGE LambdaCase #-}
45{-# LANGUAGE MultiWayIf #-}
56{-# LANGUAGE OverloadedStrings #-}
67{-# LANGUAGE ViewPatterns #-}
@@ -27,8 +28,13 @@ import qualified Data.Text as T
2728import Development.IDE hiding (line )
2829import Development.IDE.Core.Compile (sourceParser ,
2930 sourceTypecheck )
31+ import Development.IDE.Core.FileStore (getVersionedTextDoc )
3032import Development.IDE.Core.PluginUtils
3133import Development.IDE.GHC.Compat
34+ import Development.IDE.GHC.Compat.Error (GhcHint (SuggestExtension ),
35+ LanguageExtensionHint (.. ),
36+ diagnosticHints ,
37+ msgEnvelopeErrorL )
3238import Development.IDE.Plugin.Completions (ghcideCompletionsPluginPriority )
3339import Development.IDE.Plugin.Completions.Logic (getCompletionPrefixFromRope )
3440import Development.IDE.Plugin.Completions.Types (PosPrefixInfo (.. ))
@@ -69,13 +75,33 @@ data Pragma = LangExt T.Text | OptGHC T.Text
6975 deriving (Show , Eq , Ord )
7076
7177suggestPragmaProvider :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
72- suggestPragmaProvider = mkCodeActionProvider suggest
78+ suggestPragmaProvider = if ghcVersion /= GHC96 then
79+ mkCodeActionProvider suggestAddPragma
80+ else mkCodeActionProvider96 suggestAddPragma96
7381
7482suggestDisableWarningProvider :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
7583suggestDisableWarningProvider = mkCodeActionProvider $ const suggestDisableWarning
7684
77- mkCodeActionProvider :: (Maybe DynFlags -> Diagnostic -> [PragmaEdit ]) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
85+ mkCodeActionProvider :: (Maybe DynFlags -> FileDiagnostic -> [PragmaEdit ]) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
7886mkCodeActionProvider mkSuggest state _plId
87+ (LSP. CodeActionParams _ _ docId@ LSP. TextDocumentIdentifier { _uri = uri } caRange _) = do
88+ verTxtDocId <- liftIO $ runAction " classplugin.codeAction.getVersionedTextDoc" state $ getVersionedTextDoc docId
89+ normalizedFilePath <- getNormalizedFilePathE (verTxtDocId ^. L. uri)
90+ -- ghc session to get some dynflags even if module isn't parsed
91+ (hscEnv -> hsc_dflags -> sessionDynFlags, _) <-
92+ runActionE " Pragmas.GhcSession" state $ useWithStaleE GhcSession normalizedFilePath
93+ fileContents <- liftIO $ runAction " Pragmas.GetFileContents" state $ getFileContents normalizedFilePath
94+ parsedModule <- liftIO $ runAction " Pragmas.GetParsedModule" state $ getParsedModule normalizedFilePath
95+ let parsedModuleDynFlags = ms_hspp_opts . pm_mod_summary <$> parsedModule
96+ nextPragmaInfo = Pragmas. getNextPragmaInfo sessionDynFlags fileContents
97+ activeDiagnosticsInRange (shakeExtras state) normalizedFilePath caRange >>= \ case
98+ Nothing -> pure $ LSP. InL []
99+ Just fileDiags -> do
100+ let actions = concatMap (mkSuggest parsedModuleDynFlags) fileDiags
101+ pure $ LSP. InL $ pragmaEditToAction uri nextPragmaInfo <$> nubOrdOn snd actions
102+
103+ mkCodeActionProvider96 :: (Maybe DynFlags -> Diagnostic -> [PragmaEdit ]) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
104+ mkCodeActionProvider96 mkSuggest state _plId
79105 (LSP. CodeActionParams _ _ LSP. TextDocumentIdentifier { _uri = uri } _ (LSP. CodeActionContext diags _monly _)) = do
80106 normalizedFilePath <- getNormalizedFilePathE uri
81107 -- ghc session to get some dynflags even if module isn't parsed
@@ -89,7 +115,6 @@ mkCodeActionProvider mkSuggest state _plId
89115 pure $ LSP. InL $ pragmaEditToAction uri nextPragmaInfo <$> pedits
90116
91117
92-
93118-- | Add a Pragma to the given URI at the top of the file.
94119-- It is assumed that the pragma name is a valid pragma,
95120-- thus, not validated.
@@ -108,22 +133,17 @@ pragmaEditToAction uri Pragmas.NextPragmaInfo{ nextPragmaLine, lineSplitTextEdit
108133 , let LSP. TextEdit { _range, _newText } = insertTextEdit ->
109134 [LSP. TextEdit _range (render p <> _newText), deleteTextEdit]
110135 | otherwise -> [LSP. TextEdit pragmaInsertRange (render p)]
111-
112136 edit =
113137 LSP. WorkspaceEdit
114138 (Just $ M. singleton uri textEdits)
115139 Nothing
116140 Nothing
117141
118- suggest :: Maybe DynFlags -> Diagnostic -> [PragmaEdit ]
119- suggest dflags diag =
120- suggestAddPragma dflags diag
121-
122142-- ---------------------------------------------------------------------
123143
124- suggestDisableWarning :: Diagnostic -> [PragmaEdit ]
144+ suggestDisableWarning :: FileDiagnostic -> [PragmaEdit ]
125145suggestDisableWarning diagnostic
126- | Just (Just (JSON. Array attachedReasons)) <- diagnostic ^? attachedReason
146+ | Just (Just (JSON. Array attachedReasons)) <- diagnostic ^? fdLspDiagnosticL . attachedReason
127147 =
128148 [ (" Disable \" " <> w <> " \" warnings" , OptGHC w)
129149 | JSON. String attachedReason <- Foldable. toList attachedReasons
@@ -142,10 +162,24 @@ warningBlacklist =
142162
143163-- ---------------------------------------------------------------------
144164
165+ -- | Offer to add a missing Language Pragma to the top of a file.
166+ suggestAddPragma :: Maybe DynFlags -> FileDiagnostic -> [PragmaEdit ]
167+ suggestAddPragma mDynflags fd= [(" Add \" " <> r <> " \" " , LangExt r) | r <- map (T. pack . show ) $ suggestsExtension fd, r `notElem` disabled]
168+ where
169+ disabled
170+ | Just dynFlags <- mDynflags =
171+ -- GHC does not export 'OnOff', so we have to view it as string
172+ mapMaybe (T. stripPrefix " Off " . printOutputable) (extensions dynFlags)
173+ | otherwise =
174+ -- When the module failed to parse, we don't have access to its
175+ -- dynFlags. In that case, simply don't disable any pragmas.
176+ []
177+
145178-- | Offer to add a missing Language Pragma to the top of a file.
146179-- Pragmas are defined by a curated list of known pragmas, see 'possiblePragmas'.
147- suggestAddPragma :: Maybe DynFlags -> Diagnostic -> [PragmaEdit ]
148- suggestAddPragma mDynflags Diagnostic {_message, _source}
180+ -- Kept for compatibility with ghc9.6 - it is missing some structured diagnostics
181+ suggestAddPragma96 :: Maybe DynFlags -> Diagnostic -> [PragmaEdit ]
182+ suggestAddPragma96 mDynflags Diagnostic {_message, _source}
149183 | _source == Just sourceTypecheck || _source == Just sourceParser = genPragma _message
150184 where
151185 genPragma target =
@@ -158,7 +192,7 @@ suggestAddPragma mDynflags Diagnostic {_message, _source}
158192 -- When the module failed to parse, we don't have access to its
159193 -- dynFlags. In that case, simply don't disable any pragmas.
160194 []
161- suggestAddPragma _ _ = []
195+ suggestAddPragma96 _ _ = []
162196
163197-- | Find all Pragmas are an infix of the search term.
164198findPragma :: T. Text -> [T. Text ]
@@ -178,6 +212,20 @@ findPragma str = concatMap check possiblePragmas
178212 , " Strict" /= name
179213 ]
180214
215+ suggestsExtension :: FileDiagnostic -> [Extension ]
216+ suggestsExtension message = case message ^? fdStructuredMessageL . _SomeStructuredMessage . msgEnvelopeErrorL of
217+ Just s -> concat $ map (\ case
218+ SuggestExtension s -> ghcHintSuggestsExtension s
219+ _ -> [] ) (diagnosticHints s)
220+ _ -> []
221+
222+ ghcHintSuggestsExtension :: LanguageExtensionHint -> [Extension ]
223+ ghcHintSuggestsExtension (SuggestSingleExtension _ ext) = [ext]
224+ ghcHintSuggestsExtension (SuggestAnyExtension _ (ext: _)) = [ext] -- ghc suggests any of those, we pick first
225+ ghcHintSuggestsExtension (SuggestAnyExtension _ [] ) = []
226+ ghcHintSuggestsExtension (SuggestExtensions _ ext) = ext
227+ ghcHintSuggestsExtension (SuggestExtensionInOrderTo _ ext) = [ext]
228+
181229-- | All language pragmas, including the No- variants
182230allPragmas :: [T. Text ]
183231allPragmas =
0 commit comments