1
1
{-# LANGUAGE CPP #-}
2
2
{-# LANGUAGE DeriveAnyClass #-}
3
3
{-# LANGUAGE DeriveGeneric #-}
4
+ {-# LANGUAGE LambdaCase #-}
4
5
{-# LANGUAGE OverloadedStrings #-}
5
6
{-# LANGUAGE RecordWildCards #-}
6
7
{-# LANGUAGE TypeFamilies #-}
7
8
{-# LANGUAGE ViewPatterns #-}
8
9
module Ide.Plugin.Class
9
- ( descriptor
10
+ ( descriptor ,
11
+ Log (.. )
10
12
) where
11
13
12
14
import Control.Applicative
@@ -17,15 +19,17 @@ import Control.Monad.Trans.Class
17
19
import Control.Monad.Trans.Maybe
18
20
import Data.Aeson
19
21
import Data.Char
22
+ import Data.Either (rights )
20
23
import Data.List
21
24
import qualified Data.Map.Strict as Map
22
25
import Data.Maybe
23
- import qualified Data.Text as T
24
26
import qualified Data.Set as Set
27
+ import qualified Data.Text as T
25
28
import Development.IDE hiding (pluginHandlers )
26
29
import Development.IDE.Core.PositionMapping (fromCurrentRange ,
27
30
toCurrentRange )
28
- import Development.IDE.GHC.Compat as Compat hiding (locA )
31
+ import Development.IDE.GHC.Compat as Compat hiding (locA ,
32
+ (<+>) )
29
33
import Development.IDE.GHC.Compat.Util
30
34
import Development.IDE.Spans.AtPoint
31
35
import qualified GHC.Generics as Generics
@@ -40,14 +44,24 @@ import Language.LSP.Types
40
44
import qualified Language.LSP.Types.Lens as J
41
45
42
46
#if MIN_VERSION_ghc(9,2,0)
43
- import GHC.Hs (AnnsModule (AnnsModule ))
47
+ import GHC.Hs (AnnsModule (AnnsModule ))
44
48
import GHC.Parser.Annotation
45
49
#endif
46
50
47
- descriptor :: PluginId -> PluginDescriptor IdeState
48
- descriptor plId = (defaultPluginDescriptor plId)
51
+ data Log
52
+ = LogImplementedMethods Class [T. Text ]
53
+
54
+ instance Pretty Log where
55
+ pretty = \ case
56
+ LogImplementedMethods cls methods ->
57
+ pretty (" Detected implmented methods for class" :: String )
58
+ <+> pretty (show (getOccString cls) <> " :" ) -- 'show' is used here to add quotes around the class name
59
+ <+> pretty methods
60
+
61
+ descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
62
+ descriptor recorder plId = (defaultPluginDescriptor plId)
49
63
{ pluginCommands = commands
50
- , pluginHandlers = mkPluginHandler STextDocumentCodeAction codeAction
64
+ , pluginHandlers = mkPluginHandler STextDocumentCodeAction ( codeAction recorder)
51
65
}
52
66
53
67
commands :: [PluginCommand IdeState ]
@@ -176,8 +190,8 @@ addMethodPlaceholders state AddMinimalMethodsParams{..} = do
176
190
-- |
177
191
-- This implementation is ad-hoc in a sense that the diagnostic detection mechanism is
178
192
-- sensitive to the format of diagnostic messages from GHC.
179
- codeAction :: PluginMethodHandler IdeState TextDocumentCodeAction
180
- codeAction state plId (CodeActionParams _ _ docId _ context) = liftIO $ fmap (fromMaybe errorResult) . runMaybeT $ do
193
+ codeAction :: Recorder ( WithPriority Log ) -> PluginMethodHandler IdeState TextDocumentCodeAction
194
+ codeAction recorder state plId (CodeActionParams _ _ docId _ context) = liftIO $ fmap (fromMaybe errorResult) . runMaybeT $ do
181
195
docPath <- MaybeT . pure . uriToNormalizedFilePath $ toNormalizedUri uri
182
196
actions <- join <$> mapM (mkActions docPath) methodDiags
183
197
pure . Right . List $ actions
@@ -190,9 +204,17 @@ codeAction state plId (CodeActionParams _ _ docId _ context) = liftIO $ fmap (fr
190
204
methodDiags = filter (\ d -> isClassMethodWarning (d ^. J. message)) ghcDiags
191
205
192
206
mkActions docPath diag = do
193
- ident <- findClassIdentifier docPath range
207
+ (HAR {hieAst = ast}, pmap) <-
208
+ MaybeT . runAction " classplugin" state $ useWithStale GetHieAst docPath
209
+ instancePosition <- MaybeT . pure $
210
+ fromCurrentRange pmap range ^? _Just . J. start
211
+ & fmap (J. character -~ 1 )
212
+
213
+ ident <- findClassIdentifier ast instancePosition
194
214
cls <- findClassFromIdentifier docPath ident
195
- lift . traverse mkAction . minDefToMethodGroups . classMinimalDef $ cls
215
+ implemented <- findImplementedMethods ast instancePosition
216
+ logWith recorder Info (LogImplementedMethods cls implemented)
217
+ lift . traverse (mkAction . (\\ implemented)) . minDefToMethodGroups . classMinimalDef $ cls
196
218
where
197
219
range = diag ^. J. range
198
220
@@ -212,16 +234,14 @@ codeAction state plId (CodeActionParams _ _ docId _ context) = liftIO $ fmap (fr
212
234
= InR
213
235
$ CodeAction title (Just CodeActionQuickFix ) (Just (List [] )) Nothing Nothing Nothing (Just cmd) Nothing
214
236
215
- findClassIdentifier docPath range = do
216
- (hieAstResult, pmap) <- MaybeT . runAction " classplugin" state $ useWithStale GetHieAst docPath
217
- case hieAstResult of
218
- HAR {hieAst = hf} ->
219
- pure
220
- $ head . head
221
- $ pointCommand hf (fromJust (fromCurrentRange pmap range) ^. J. start & J. character -~ 1 )
222
- ( (Map. keys . Map. filter isClassNodeIdentifier . Compat. getNodeIds)
223
- <=< nodeChildren
224
- )
237
+ findClassIdentifier :: HieASTs a -> Position -> MaybeT IO (Either ModuleName Name )
238
+ findClassIdentifier ast instancePosition =
239
+ pure
240
+ $ head . head
241
+ $ pointCommand ast instancePosition
242
+ ( (Map. keys . Map. filter isClassNodeIdentifier . Compat. getNodeIds)
243
+ <=< nodeChildren
244
+ )
225
245
226
246
findClassFromIdentifier docPath (Right name) = do
227
247
(hscEnv -> hscenv, _) <- MaybeT . runAction " classplugin" state $ useWithStale GhcSessionDeps docPath
@@ -234,18 +254,38 @@ codeAction state plId (CodeActionParams _ _ docId _ context) = liftIO $ fmap (fr
234
254
_ -> panic " Ide.Plugin.Class.findClassFromIdentifier"
235
255
findClassFromIdentifier _ (Left _) = panic " Ide.Plugin.Class.findClassIdentifier"
236
256
257
+ findImplementedMethods :: HieASTs a -> Position -> MaybeT IO [T. Text ]
258
+ findImplementedMethods asts instancePosition = do
259
+ pure
260
+ $ concat
261
+ $ pointCommand asts instancePosition
262
+ $ map (T. pack . getOccString) . rights . findInstanceValBindIdentifiers
263
+
264
+ -- | Recurses through the given AST to find identifiers which are
265
+ -- 'InstanceValBind's.
266
+ findInstanceValBindIdentifiers :: HieAST a -> [Identifier ]
267
+ findInstanceValBindIdentifiers ast =
268
+ let valBindIds = Map. keys
269
+ . Map. filter (any isInstanceValBind . identInfo)
270
+ $ getNodeIds ast
271
+ in valBindIds <> concatMap findInstanceValBindIdentifiers (nodeChildren ast)
272
+
237
273
ghostSpan :: RealSrcSpan
238
274
ghostSpan = realSrcLocSpan $ mkRealSrcLoc (fsLit " <haskell-language-sever>" ) 1 1
239
275
240
276
containRange :: Range -> SrcSpan -> Bool
241
277
containRange range x = isInsideSrcSpan (range ^. J. start) x || isInsideSrcSpan (range ^. J. end) x
242
278
243
279
isClassNodeIdentifier :: IdentifierDetails a -> Bool
244
- isClassNodeIdentifier ident = (isNothing . identType) ident && Use `Set.member` ( identInfo ident)
280
+ isClassNodeIdentifier ident = (isNothing . identType) ident && Use `Set.member` identInfo ident
245
281
246
282
isClassMethodWarning :: T. Text -> Bool
247
283
isClassMethodWarning = T. isPrefixOf " • No explicit implementation for"
248
284
285
+ isInstanceValBind :: ContextInfo -> Bool
286
+ isInstanceValBind (ValBind InstanceBind _ _) = True
287
+ isInstanceValBind _ = False
288
+
249
289
minDefToMethodGroups :: BooleanFormula Name -> [[T. Text ]]
250
290
minDefToMethodGroups = go
251
291
where
0 commit comments