|
| 1 | +{-# LANGUAGE GADTs #-} |
| 2 | +{-# LANGUAGE LambdaCase #-} |
| 3 | +{-# LANGUAGE OverloadedStrings #-} |
| 4 | + |
| 5 | +module Ide.Plugin.UnderlyingType where |
| 6 | + |
| 7 | +import Control.Lens ((^.)) |
| 8 | +import Control.Monad (forM) |
| 9 | +import Control.Monad.IO.Class |
| 10 | +import Control.Monad.RWS (lift) |
| 11 | +import qualified Data.Aeson as Aeson |
| 12 | +import Data.Either (rights) |
| 13 | +import qualified Data.Map as M |
| 14 | +import Data.Maybe (catMaybes, fromMaybe) |
| 15 | +import qualified Data.Text as T |
| 16 | +import Development.IDE hiding (pluginHandlers) |
| 17 | +import Development.IDE.Core.PluginUtils |
| 18 | +import Development.IDE.Core.PositionMapping |
| 19 | +import Development.IDE.Core.Shake (getShakeExtras) |
| 20 | +import Development.IDE.GHC.Compat.Core (Name) |
| 21 | +import Development.IDE.Spans.AtPoint (pointCommand) |
| 22 | +import GHC.Iface.Ext.Types |
| 23 | +import GHC.Iface.Ext.Utils (nodeInfo) |
| 24 | +import Ide.Plugin.Error (getNormalizedFilePathE) |
| 25 | +import Ide.Types |
| 26 | +import qualified Language.LSP.Protocol.Lens as L |
| 27 | +import Language.LSP.Protocol.Message (Method (Method_TextDocumentCodeAction), |
| 28 | + SMethod (SMethod_TextDocumentCodeAction)) |
| 29 | +import Language.LSP.Protocol.Types |
| 30 | + |
| 31 | +data Log |
| 32 | + = LogPluginCalled Uri Range |
| 33 | + | LogIdentifiersFound Position [Name] |
| 34 | + | LogTypeLocationsFound [(Location, Identifier)] |
| 35 | + | LogProcessingLocation Location Identifier |
| 36 | + | LogActionCreated Name Name |
| 37 | + |
| 38 | +instance Pretty Log where |
| 39 | + pretty = \case |
| 40 | + LogPluginCalled uri range -> |
| 41 | + "Plugin called for" <+> pretty (show uri) <+> "at range" <+> pretty (show range) |
| 42 | + LogIdentifiersFound pos identifiers -> |
| 43 | + "Found" <+> pretty (length identifiers) <+> "identifiers at" <+> pretty (show pos) <> ":" <+> |
| 44 | + pretty (T.intercalate ", " (map printOutputable identifiers)) |
| 45 | + LogTypeLocationsFound locations -> |
| 46 | + "Found" <+> pretty (length locations) <+> "type locations" |
| 47 | + LogProcessingLocation loc identifier -> |
| 48 | + "Processing location" <+> pretty (show loc) <+> "for" <+> pretty (printOutputable identifier) |
| 49 | + LogActionCreated varName typeName -> |
| 50 | + "Created action for" <+> pretty (printOutputable varName) <+> "->" <+> pretty (printOutputable typeName) |
| 51 | + |
| 52 | +descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState |
| 53 | +descriptor recorder plId = |
| 54 | + (defaultPluginDescriptor plId "Generates actions for going to the underlying type's definition.") |
| 55 | + { pluginHandlers = mkPluginHandler SMethod_TextDocumentCodeAction (provider recorder) |
| 56 | + } |
| 57 | + |
| 58 | +provider :: Recorder (WithPriority Log) -> PluginMethodHandler IdeState 'Method_TextDocumentCodeAction |
| 59 | +provider recorder state _ (CodeActionParams _ _ (TextDocumentIdentifier uri) range _) = do |
| 60 | + logWith recorder Debug $ LogPluginCalled uri range |
| 61 | + |
| 62 | + nfp <- getNormalizedFilePathE uri |
| 63 | + |
| 64 | + (HAR _ hieAst _ _ hieKind, posMapping) <- |
| 65 | + runActionE "GetHieAst" state $ |
| 66 | + useWithStaleE GetHieAst nfp |
| 67 | + |
| 68 | + let position = fromMaybe (range ^. L.start) $ fromCurrentPosition posMapping (range ^. L.start) |
| 69 | + namesAtPosition = mconcat $ pointCommand hieAst position (extractNamesAtPosition hieKind) |
| 70 | + |
| 71 | + logWith recorder Debug $ LogIdentifiersFound position namesAtPosition |
| 72 | + |
| 73 | + actions <- case namesAtPosition of |
| 74 | + [] -> pure [] |
| 75 | + identifiers -> do |
| 76 | + typeActions <- forM identifiers $ \bindingName -> do |
| 77 | + locationForIdentifierType <- runActionE "TypeCheck" state $ do |
| 78 | + shakeExtras <- lift getShakeExtras |
| 79 | + result <- liftIO $ runIdeAction "Get Type Definition" shakeExtras $ getTypeDefinition nfp position |
| 80 | + pure $ fromMaybe [] result |
| 81 | + |
| 82 | + logWith recorder Debug $ LogTypeLocationsFound locationForIdentifierType |
| 83 | + |
| 84 | + typeDefActions <- forM locationForIdentifierType $ \(loc, identifier) -> do |
| 85 | + logWith recorder Debug $ LogProcessingLocation loc identifier |
| 86 | + |
| 87 | + case identifier of |
| 88 | + Left _moduleName -> pure Nothing |
| 89 | + Right underlyingTypeName -> do |
| 90 | + logWith recorder Debug $ LogActionCreated bindingName underlyingTypeName |
| 91 | + pure $ Just $ createGoToTypeDefAction bindingName underlyingTypeName loc |
| 92 | + |
| 93 | + pure $ catMaybes typeDefActions |
| 94 | + |
| 95 | + pure $ mconcat typeActions |
| 96 | + |
| 97 | + pure $ InL actions |
| 98 | + |
| 99 | +extractNamesAtPosition :: HieKind a -> HieAST a -> [Name] |
| 100 | +extractNamesAtPosition hieKind ast = |
| 101 | + case hieKind of |
| 102 | + HieFresh -> rights $ map fst $ M.toList $ nodeIdentifiers $ nodeInfo ast |
| 103 | + HieFromDisk {} -> [] |
| 104 | + |
| 105 | +createGoToTypeDefAction :: Name -> Name -> Location -> (Command |? CodeAction) |
| 106 | +createGoToTypeDefAction boundVarName underlyingTypeName loc = do |
| 107 | + let defRange = loc ^. L.range |
| 108 | + InR $ |
| 109 | + CodeAction |
| 110 | + ("Go to definition of " <> printOutputable underlyingTypeName <> " (inferred from " <> printOutputable boundVarName <> "'s type)") |
| 111 | + (Just $ CodeActionKind_Custom "GoToUnderlyingTypeDefinition") |
| 112 | + Nothing |
| 113 | + Nothing |
| 114 | + Nothing |
| 115 | + Nothing |
| 116 | + ( Just $ |
| 117 | + Command |
| 118 | + "Go to definition" |
| 119 | + -- TODO: How to decouple this from VS code? |
| 120 | + "vscode.open" |
| 121 | + ( Just |
| 122 | + [ Aeson.toJSON $ loc ^. L.uri, |
| 123 | + Aeson.object |
| 124 | + [ "selection" |
| 125 | + Aeson..= Aeson.object |
| 126 | + [ "start" |
| 127 | + Aeson..= Aeson.object |
| 128 | + [ "line" Aeson..= (defRange ^. L.start . L.line), |
| 129 | + "character" Aeson..= (defRange ^. L.start . L.character) |
| 130 | + ], |
| 131 | + "end" |
| 132 | + Aeson..= Aeson.object |
| 133 | + [ "line" Aeson..= (defRange ^. L.end . L.line), |
| 134 | + "character" Aeson..= (defRange ^. L.end . L.character) |
| 135 | + ] |
| 136 | + ] |
| 137 | + ] |
| 138 | + ] |
| 139 | + ) |
| 140 | + ) |
| 141 | + Nothing |
0 commit comments