Skip to content

Commit 90eb271

Browse files
committed
Use structured diagnostics in pragmas plugin
Changes suggestion provider in pragmas plugin to use structured diagnostics and ghc machinery to generate hints
1 parent 349ff6e commit 90eb271

File tree

2 files changed

+62
-13
lines changed

2 files changed

+62
-13
lines changed

haskell-language-server.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,7 @@ library hls-pragmas-plugin
905905
, text
906906
, transformers
907907
, containers
908+
, ghc
908909

909910
test-suite hls-pragmas-plugin-tests
910911
import: defaults, pedantic, test-defaults, warnings

plugins/hls-pragmas-plugin/src/Ide/Plugin/Pragmas.hs

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{-# LANGUAGE CPP #-}
22
{-# LANGUAGE DataKinds #-}
33
{-# LANGUAGE DuplicateRecordFields #-}
4+
{-# LANGUAGE LambdaCase #-}
45
{-# LANGUAGE MultiWayIf #-}
56
{-# LANGUAGE OverloadedStrings #-}
67
{-# LANGUAGE ViewPatterns #-}
@@ -27,12 +28,17 @@ import qualified Data.Text as T
2728
import Development.IDE hiding (line)
2829
import Development.IDE.Core.Compile (sourceParser,
2930
sourceTypecheck)
31+
import Development.IDE.Core.FileStore (getVersionedTextDoc)
3032
import Development.IDE.Core.PluginUtils
3133
import Development.IDE.GHC.Compat
34+
import Development.IDE.GHC.Compat.Error (msgEnvelopeErrorL)
3235
import Development.IDE.Plugin.Completions (ghcideCompletionsPluginPriority)
3336
import Development.IDE.Plugin.Completions.Logic (getCompletionPrefixFromRope)
3437
import Development.IDE.Plugin.Completions.Types (PosPrefixInfo (..))
3538
import qualified Development.IDE.Spans.Pragmas as Pragmas
39+
import GHC.Types.Error (GhcHint (SuggestExtension),
40+
LanguageExtensionHint (..),
41+
diagnosticHints)
3642
import Ide.Plugin.Error
3743
import Ide.Types
3844
import qualified Language.LSP.Protocol.Lens as L
@@ -69,13 +75,33 @@ data Pragma = LangExt T.Text | OptGHC T.Text
6975
deriving (Show, Eq, Ord)
7076

7177
suggestPragmaProvider :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
72-
suggestPragmaProvider = mkCodeActionProvider suggest
78+
suggestPragmaProvider = if ghcVersion /= GHC96 then
79+
mkCodeActionProvider suggestAddPragma
80+
else mkCodeActionProvider96 suggestAddPragma96
7381

7482
suggestDisableWarningProvider :: PluginMethodHandler IdeState 'LSP.Method_TextDocumentCodeAction
7583
suggestDisableWarningProvider = 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
7886
mkCodeActionProvider 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]
125145
suggestDisableWarning 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.
164198
findPragma :: 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
182230
allPragmas :: [T.Text]
183231
allPragmas =

0 commit comments

Comments
 (0)