1
+ {-# LANGUAGE ViewPatterns #-}
1
2
{-# LANGUAGE DeriveAnyClass #-}
2
3
{-# LANGUAGE DeriveGeneric #-}
3
4
{-# LANGUAGE DerivingStrategies #-}
@@ -22,13 +23,11 @@ import qualified Data.Text as T
22
23
import Development.IDE.Core.RuleTypes (GhcSessionDeps (GhcSessionDeps ),
23
24
TcModuleResult (tmrModule ),
24
25
TypeCheck (TypeCheck ))
25
- import Development.IDE.Core.Service (IdeState , runAction )
26
26
import Development.IDE.Core.Shake (IdeAction , IdeState (.. ),
27
- runIdeAction , useWithStaleFast ,
28
- use_ )
27
+ runIdeAction , useWithStaleFast )
29
28
import Development.IDE.GHC.Compat
30
29
import Development.IDE.GHC.Error (realSpan , realSrcSpanToRange )
31
- import Development.IDE.GHC.Util (hscEnv , prettyPrint )
30
+ import Development.IDE.GHC.Util (HscEnvEq , hscEnv , prettyPrint )
32
31
import GHC.Generics (Generic )
33
32
import Ide.Plugin
34
33
import Ide.Types
@@ -42,46 +41,64 @@ import TcRnTypes (TcGblEnv (tcg_used_gres))
42
41
importCommandId :: CommandId
43
42
importCommandId = " ImportLensCommand"
44
43
44
+ -- | The "main" function of a plugin
45
45
descriptor :: PluginId -> PluginDescriptor
46
46
descriptor plId = (defaultPluginDescriptor plId) {
47
+ -- This plugin provides code lenses
47
48
pluginCodeLensProvider = Just provider,
49
+ -- This plugin provides a command handler
48
50
pluginCommands = [ importLensCommand ]
49
51
}
50
52
53
+ -- | The command descriptor
51
54
importLensCommand :: PluginCommand
52
55
importLensCommand =
53
56
PluginCommand importCommandId " Explicit import command" runImportCommand
54
57
58
+ -- | The type of the parameters accepted by our command
55
59
data ImportCommandParams = ImportCommandParams WorkspaceEdit
56
60
deriving Generic
57
61
deriving anyclass (FromJSON , ToJSON )
58
62
63
+ -- | The actual command handler
59
64
runImportCommand :: CommandFunction ImportCommandParams
60
65
runImportCommand _lspFuncs _state (ImportCommandParams edit) = do
66
+ -- This command simply triggers a workspace edit!
61
67
return (Right Null , Just (WorkspaceApplyEdit , ApplyWorkspaceEditParams edit))
62
68
63
- -- For every implicit import statement,
64
- -- return a code lens of the corresponding explicit import
65
- -- Example. For the module below:
69
+ -- | For every implicit import statement, return a code lens of the corresponding explicit import
70
+ -- Example - for the module below:
66
71
--
67
72
-- > import Data.List
68
73
-- >
69
74
-- > f = intercalate " " . sortBy length
70
75
--
71
- -- the provider should produce one code lens:
76
+ -- the provider should produce one code lens associated to the import statement :
72
77
--
73
78
-- > import Data.List (intercalate, sortBy)
74
-
75
79
provider :: CodeLensProvider
76
- provider _lspFuncs state pId CodeLensParams {.. }
77
- | TextDocumentIdentifier {_uri} <- _textDocument
78
- , Just nfp <- uriToNormalizedFilePath $ toNormalizedUri _uri
80
+ provider _lspFuncs -- LSP functions, not used
81
+ state -- ghcide state, used to retrieve typechecking artifacts
82
+ pId -- plugin Id
83
+ CodeLensParams {_textDocument = TextDocumentIdentifier {_uri}}
84
+ -- VSCode uses URIs instead of file paths
85
+ -- haskell-lsp provides conversion functions
86
+ | Just nfp <- uriToNormalizedFilePath $ toNormalizedUri _uri
79
87
= do
80
- Just (tmr, _) <- runIde state $ useWithStaleFast TypeCheck nfp
81
- hsc <- hscEnv <$> runAction " importLens" state (use_ GhcSessionDeps nfp)
82
- (imports, mbMinImports) <- extractMinimalImports hsc (tmrModule tmr)
88
+ -- Get the typechecking artifacts from the module, even if they are stale.
89
+ -- This is for responsiveness - we don't want our code lenses to vanish
90
+ -- just because there is a type error unrelated to the moduel imports.
91
+ -- However, if the user edits the imports while the module does not typecheck,
92
+ -- our code lenses will get out of sync
93
+ tmr <- runIde state $ useWithStaleFast TypeCheck nfp
94
+ -- We also need a GHC session with all the dependencies
95
+ hsc <- runIde state $ useWithStaleFast GhcSessionDeps nfp
96
+ -- Use the GHC api to extract the "minimal" imports
97
+ (imports, mbMinImports) <- extractMinimalImports hsc tmr
83
98
84
99
case mbMinImports of
100
+ -- Implement the provider logic:
101
+ -- for every import, if it's lacking a explicit list, generate a code lens
85
102
Just minImports -> do
86
103
let minImportsMap =
87
104
Map. fromList [ (srcSpanStart l, i) | L l i <- minImports ]
@@ -93,41 +110,63 @@ provider _lspFuncs state pId CodeLensParams{..}
93
110
| otherwise
94
111
= return $ Right (List [] )
95
112
96
- extractMinimalImports :: HscEnv -> TypecheckedModule -> IO ([LImportDecl GhcRn ], Maybe [LImportDecl GhcRn ])
97
- extractMinimalImports hsc TypecheckedModule {.. } = do
113
+ -- | Use the ghc api to extract a minimal, explicit set of imports for this module
114
+ extractMinimalImports
115
+ :: Maybe (HscEnvEq , a )
116
+ -> Maybe (TcModuleResult , b )
117
+ -> IO ([LImportDecl GhcRn ], Maybe [LImportDecl GhcRn ])
118
+ extractMinimalImports (Just (hsc, _)) (Just (tmrModule -> TypecheckedModule {.. }, _)) = do
119
+ -- extract the original imports and the typechecking environment
98
120
let (tcEnv,_) = tm_internals_
99
121
Just (_, imports, _, _) = tm_renamed_source
100
122
ParsedModule { pm_parsed_source = L loc _} = tm_parsed_module
123
+ span = fromMaybe (error " expected real" ) $ realSpan loc
101
124
125
+ -- GHC is secretly full of mutable state
102
126
gblElts <- readIORef (tcg_used_gres tcEnv)
127
+
128
+ -- call findImportUsage does exactly what we need
129
+ -- GHC is full of treats like this
103
130
let usage = findImportUsage imports gblElts
104
- span = fromMaybe (error " expected real" ) $ realSpan loc
105
- (_, minimalImports) <- initTcWithGbl hsc tcEnv span $ getMinimalImports usage
131
+ (_, minimalImports) <- initTcWithGbl (hscEnv hsc) tcEnv span $ getMinimalImports usage
132
+
133
+ -- return both the original imports and the computed minimal ones
106
134
return (imports, minimalImports)
107
135
136
+ extractMinimalImports _ _ = return ([] , Nothing )
137
+
138
+ -- | Given an import declaration, generate a code lens unless it has an explicit import list
108
139
generateLens :: PluginId -> Uri -> Map SrcLoc (ImportDecl GhcRn ) -> LImportDecl GhcRn -> IO (Maybe CodeLens )
109
140
generateLens pId uri minImports (L src imp)
141
+ -- Explicit import list case
110
142
| ImportDecl {ideclHiding = Just (False ,_)} <- imp
111
143
= return Nothing
144
+ -- No explicit import list
112
145
| RealSrcSpan l <- src
113
146
, Just explicit <- Map. lookup (srcSpanStart src) minImports
114
147
, L _ mn <- ideclName imp
148
+ -- (almost) no one wants to see an explicit import list for Prelude
115
149
, mn /= moduleName pRELUDE
116
150
= do
151
+ -- The title of the command is just the minimal explicit import decl
117
152
let title = T. pack $ prettyPrint explicit
118
- commandArgs = Nothing
119
- c <- mkLspCommand pId importCommandId title commandArgs
120
- let _range :: Range = realSrcSpanToRange l
153
+ -- the range of the code lens is the span of the original import decl
154
+ _range :: Range = realSrcSpanToRange l
155
+ -- the code lens has no extra data
121
156
_xdata = Nothing
157
+ -- an edit that replaces the whole declaration with the explicit one
122
158
edit = WorkspaceEdit (Just editsMap) Nothing
123
159
editsMap = HashMap. fromList [(uri, List [importEdit])]
124
160
importEdit = TextEdit _range title
125
- args = ImportCommandParams edit
126
- _arguments = Just (List [toJSON args])
127
- _command = Just (c :: Command ){_arguments}
161
+ -- the command argument is simply the edit
162
+ _arguments = Just [toJSON $ ImportCommandParams edit]
163
+ -- create the command
164
+ _command <- Just <$> mkLspCommand pId importCommandId title _arguments
165
+ -- create and return the code lens
128
166
return $ Just CodeLens {.. }
129
167
| otherwise
130
168
= return Nothing
131
169
170
+ -- | A helper to run ide actions
132
171
runIde :: IdeState -> IdeAction a -> IO a
133
172
runIde state = runIdeAction " importLens" (shakeExtras state)
0 commit comments