1
1
{-# LANGUAGE OverloadedStrings #-}
2
2
{-# LANGUAGE DeriveGeneric #-}
3
3
{-# LANGUAGE DeriveAnyClass #-}
4
- {-# LANGUAGE TupleSections #-}
5
4
{-# LANGUAGE LambdaCase #-}
6
5
module Haskell.Ide.Engine.Plugin.HsImport where
7
6
8
7
import Control.Lens.Operators
9
8
import Control.Monad.IO.Class
10
9
import Control.Monad
11
10
import Data.Aeson
12
- import Data.Bitraversable
13
- import Data.Bifunctor
14
11
import Data.Foldable
15
12
import Data.Maybe
16
13
import Data.Monoid ( (<>) )
17
14
import qualified Data.Text as T
18
15
import qualified Data.Text.IO as T
19
16
import qualified GHC.Generics as Generics
20
17
import qualified GhcModCore as GM ( mkRevRedirMapFunc , withMappedFile )
21
- import HsImport
18
+ import qualified HsImport
22
19
import Haskell.Ide.Engine.Config
23
20
import Haskell.Ide.Engine.MonadTypes
24
21
import qualified Haskell.Ide.Engine.Support.HieExtras as Hie
@@ -29,6 +26,7 @@ import qualified Haskell.Ide.Engine.Plugin.Hoogle
29
26
as Hoogle
30
27
import System.Directory
31
28
import System.IO
29
+ import qualified Safe as S
32
30
33
31
hsimportDescriptor :: PluginId -> PluginDescriptor
34
32
hsimportDescriptor plId = PluginDescriptor
@@ -43,28 +41,70 @@ hsimportDescriptor plId = PluginDescriptor
43
41
, pluginFormattingProvider = Nothing
44
42
}
45
43
44
+ data SymbolType
45
+ = Symbol
46
+ | Constructor
47
+ | Type
48
+ deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
49
+
50
+
51
+ -- | What of the symbol should be taken.
52
+ data SymbolKind
53
+ = Only SymbolName -- ^ only the symbol should be taken
54
+ | AllOf DatatypeName -- ^ all constructors or methods of the symbol should be taken: Symbol(..)
55
+ | OneOf DatatypeName SymbolName -- ^ some constructors or methods of the symbol should be taken: Symbol(X, Y)
56
+ deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
57
+
58
+ -- | The imported or from the import hidden symbol.
59
+ data SymbolImport a
60
+ = Import a -- ^ the symbol to import
61
+ | Hiding a -- ^ the symbol to hide from the import
62
+ deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
63
+
64
+
65
+ extractSymbolImport :: SymbolImport a -> a
66
+ extractSymbolImport (Hiding s) = s
67
+ extractSymbolImport (Import s) = s
68
+
69
+ type ModuleName = T. Text
70
+ type SymbolName = T. Text
71
+ type DatatypeName = T. Text
72
+
73
+ data ImportStyle
74
+ = Simple -- ^ Import the whole module
75
+ | Complex (SymbolImport SymbolKind ) -- ^ Complex operation, import module hiding symbols or import only selected symbols.
76
+ deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
77
+
78
+ data ImportDiagnostic = ImportDiagnostic
79
+ { diagnostic :: J. Diagnostic
80
+ , term :: SymbolName
81
+ , termType :: SymbolImport SymbolType
82
+ }
83
+ deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
84
+
85
+
46
86
-- | Import Parameters for Modules.
47
87
-- Can be used to import every symbol from a module,
48
88
-- or to import only a specific function from a module.
49
89
data ImportParams = ImportParams
50
- { file :: Uri -- ^ Uri to the file to import the module to.
51
- , addToImportList :: Maybe T. Text -- ^ If set, an import-list will be created.
52
- , moduleToImport :: T. Text -- ^ Name of the module to import.
90
+ { file :: Uri -- ^ Uri to the file to import the module to.
91
+ , importStyle :: ImportStyle -- ^ How to import the module
92
+ , moduleToImport :: ModuleName -- ^ Name of the module to import.
53
93
}
54
94
deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
55
95
56
96
importCmd :: CommandFunc ImportParams J. WorkspaceEdit
57
- importCmd = CmdSync $ \ (ImportParams uri importList modName) ->
58
- importModule uri importList modName
97
+ importCmd = CmdSync $ \ (ImportParams uri style modName) ->
98
+ importModule uri style modName
59
99
60
100
-- | Import the given module for the given file.
61
101
-- May take an explicit function name to perform an import-list import.
62
102
-- Multiple import-list imports will result in merged imports,
63
103
-- e.g. two consecutive imports for the same module will result in a single
64
104
-- import line.
65
105
importModule
66
- :: Uri -> Maybe T. Text -> T. Text -> IdeGhcM (IdeResult J. WorkspaceEdit )
67
- importModule uri importList modName =
106
+ :: Uri -> ImportStyle -> ModuleName -> IdeGhcM (IdeResult J. WorkspaceEdit )
107
+ importModule uri impStyle modName =
68
108
pluginGetFile " hsimport cmd: " uri $ \ origInput -> do
69
109
shouldFormat <- formatOnImportOn <$> getConfig
70
110
fileMap <- GM. mkRevRedirMapFunc
@@ -73,13 +113,9 @@ importModule uri importList modName =
73
113
tmpDir <- liftIO getTemporaryDirectory
74
114
(output, outputH) <- liftIO $ openTempFile tmpDir " hsimportOutput"
75
115
liftIO $ hClose outputH
76
- let args = defaultArgs { moduleName = T. unpack modName
77
- , inputSrcFile = input
78
- , symbolName = T. unpack $ fromMaybe " " importList
79
- , outputSrcFile = output
80
- }
116
+ let args = importStyleToHsImportArgs input output modName impStyle
81
117
-- execute hsimport on the given file and write into a temporary file.
82
- maybeErr <- liftIO $ hsimportWithArgs defaultConfig args
118
+ maybeErr <- liftIO $ HsImport. hsimportWithArgs HsImport. defaultConfig args
83
119
case maybeErr of
84
120
Just err -> do
85
121
liftIO $ removeFile output
@@ -153,6 +189,29 @@ importModule uri importList modName =
153
189
$ IdeResultOk (J. WorkspaceEdit newChanges newDocChanges)
154
190
else return $ IdeResultOk (J. WorkspaceEdit mChanges mDocChanges)
155
191
192
+ importStyleToHsImportArgs
193
+ :: FilePath -> FilePath -> ModuleName -> ImportStyle -> HsImport. HsImportArgs
194
+ importStyleToHsImportArgs input output modName style =
195
+ let defaultArgs =
196
+ HsImport. defaultArgs { HsImport. moduleName = T. unpack modName
197
+ , HsImport. inputSrcFile = input
198
+ , HsImport. outputSrcFile = output
199
+ }
200
+ kindToArgs kind = case kind of
201
+ Only sym -> defaultArgs { HsImport. symbolName = T. unpack sym }
202
+ OneOf dt sym -> defaultArgs { HsImport. symbolName = T. unpack dt
203
+ , HsImport. with = [T. unpack sym]
204
+ }
205
+ AllOf dt -> defaultArgs { HsImport. symbolName = T. unpack dt
206
+ , HsImport. all = True
207
+ }
208
+ in case style of
209
+ Simple -> defaultArgs
210
+ Complex s -> case s of
211
+ Hiding kind -> kindToArgs kind {- TODO: wait for hsimport version bump -}
212
+ Import kind -> kindToArgs kind
213
+
214
+
156
215
-- | Search style for Hoogle.
157
216
-- Can be used to look either for the exact term,
158
217
-- only the exact name or a relaxed form of the term.
@@ -188,28 +247,23 @@ codeActionProvider plId docId _ context = do
188
247
--
189
248
-- Result may produce several import actions, or none.
190
249
importActionsForTerms
191
- :: SearchStyle -> [(J. Diagnostic , T. Text )] -> IdeM [J. CodeAction ]
192
- importActionsForTerms style terms = do
193
- let searchTerms = map (bimap id (applySearchStyle style)) terms
194
- -- Get the function names for a nice import-list title.
195
- let functionNames = map (head . T. words . snd ) terms
196
- searchResults' <- mapM (bimapM return Hoogle. searchModules) searchTerms
197
- let searchResults = zip functionNames searchResults'
198
- let normalise =
199
- concatMap (\ (a, b) -> zip (repeat a) (concatTerms b)) searchResults
200
-
201
- concat <$> mapM (uncurry (termToActions style)) normalise
250
+ :: SearchStyle -> [ImportDiagnostic ] -> IdeM [J. CodeAction ]
251
+ importActionsForTerms style importDiagnostics = do
252
+ let searchTerms = map (applySearchStyle style . term) importDiagnostics
253
+ searchResults <- mapM Hoogle. searchModules searchTerms
254
+ let importTerms = zip searchResults importDiagnostics
255
+ concat <$> mapM (uncurry (termToActions style)) importTerms
202
256
203
257
-- | Apply the search style to given term.
204
258
-- Can be used to look for a term that matches exactly the search term,
205
259
-- or one that matches only the exact name.
206
260
-- At last, a custom relaxation function can be passed for more control.
207
261
applySearchStyle :: SearchStyle -> T. Text -> T. Text
208
- applySearchStyle Exact term = " is:exact " <> term
209
- applySearchStyle ExactName term = case T. words term of
210
- [] -> term
262
+ applySearchStyle Exact termName = " is:exact " <> termName
263
+ applySearchStyle ExactName termName = case T. words termName of
264
+ [] -> termName
211
265
(x : _) -> " is:exact " <> x
212
- applySearchStyle (Relax relax) term = relax term
266
+ applySearchStyle (Relax relax) termName = relax termName
213
267
214
268
-- | Turn a search term with function name into Import Actions.
215
269
-- Function name may be of only the exact phrase to import.
@@ -224,55 +278,121 @@ codeActionProvider plId docId _ context = do
224
278
-- no import list can be offered, since the function name
225
279
-- may be not the one we expect.
226
280
termToActions
227
- :: SearchStyle -> T. Text -> (J. Diagnostic , T. Text ) -> IdeM [J. CodeAction ]
228
- termToActions style functionName (diagnostic, termName) = do
229
- let useImportList = case style of
230
- Relax _ -> Nothing
231
- _ -> Just (mkImportAction (Just functionName) diagnostic termName)
232
- catMaybes <$> sequenceA
233
- (mkImportAction Nothing diagnostic termName : maybeToList useImportList)
281
+ :: SearchStyle -> [ModuleName ] -> ImportDiagnostic -> IdeM [J. CodeAction ]
282
+ termToActions style modules impDiagnostic =
283
+ concat <$> mapM (importModuleAction style impDiagnostic) modules
284
+
285
+ importModuleAction
286
+ :: SearchStyle -> ImportDiagnostic -> ModuleName -> IdeM [J. CodeAction ]
287
+ importModuleAction searchStyle impDiagnostic moduleName =
288
+ catMaybes <$> sequenceA codeActions
289
+ where
290
+ importListActions :: [IdeM (Maybe J. CodeAction )]
291
+ importListActions = case searchStyle of
292
+ Relax _ -> []
293
+ _ -> catMaybes
294
+ $ case extractSymbolImport $ termType impDiagnostic of
295
+ Symbol
296
+ -> [ mkImportAction moduleName impDiagnostic . Just . Only
297
+ <$> symName (term impDiagnostic)
298
+ ]
299
+ Constructor
300
+ -> [ mkImportAction moduleName impDiagnostic . Just . AllOf
301
+ <$> datatypeName (term impDiagnostic)
302
+ , (\ dt sym -> mkImportAction moduleName impDiagnostic . Just
303
+ $ OneOf dt sym)
304
+ <$> datatypeName (term impDiagnostic)
305
+ <*> symName (term impDiagnostic)
306
+ ]
307
+ Type
308
+ -> [ mkImportAction moduleName impDiagnostic . Just . Only
309
+ <$> symName (term impDiagnostic)]
310
+
311
+ codeActions :: [IdeM (Maybe J. CodeAction )]
312
+ codeActions = case termType impDiagnostic of
313
+ Hiding _ -> []
314
+ Import _ -> [mkImportAction moduleName impDiagnostic Nothing ]
315
+ ++ importListActions
316
+
317
+ signatureOf :: T. Text -> Maybe T. Text
318
+ signatureOf sig = do
319
+ let parts = T. splitOn " ::" sig
320
+ typeSig <- S. tailMay parts
321
+ S. headMay typeSig
322
+
323
+ datatypeName :: T. Text -> Maybe T. Text
324
+ datatypeName sig = do
325
+ sig_ <- signatureOf sig
326
+ let sigParts = T. splitOn " ->" sig_
327
+ lastPart <- S. lastMay sigParts
328
+ let dtNameSig = T. words lastPart
329
+ qualifiedDtName <- S. headMay dtNameSig
330
+ let qualifiedDtNameParts = T. splitOn " ." qualifiedDtName
331
+ S. lastMay qualifiedDtNameParts
332
+
333
+ symName :: T. Text -> Maybe SymbolName
334
+ symName = S. headMay . T. words
234
335
235
- concatTerms :: (a , [b ]) -> [(a , b )]
236
- concatTerms (a, b) = zip (repeat a) b
237
336
238
337
-- TODO: Check if package is already installed
239
338
mkImportAction
240
- :: Maybe T. Text -> J. Diagnostic -> T. Text -> IdeM (Maybe J. CodeAction )
241
- mkImportAction importList diag modName = do
339
+ :: ModuleName -> ImportDiagnostic -> Maybe SymbolKind -> IdeM (Maybe J. CodeAction )
340
+ mkImportAction modName importDiagnostic symbolType = do
242
341
cmd <- mkLspCommand plId " import" title (Just cmdParams)
243
342
return (Just (codeAction cmd))
244
343
where
245
344
codeAction cmd = J. CodeAction title
246
345
(Just J. CodeActionQuickFix )
247
- (Just (J. List [diag ]))
346
+ (Just (J. List [diagnostic importDiagnostic ]))
248
347
Nothing
249
348
(Just cmd)
250
- title =
251
- " Import module "
252
- <> modName
253
- <> maybe " " (\ name -> " (" <> name <> " )" ) importList
254
- cmdParams = [toJSON (ImportParams (docId ^. J. uri) importList modName)]
349
+ title = " Import module "
350
+ <> modName
351
+ <> case termType importDiagnostic of
352
+ Hiding _ -> " hiding "
353
+ Import _ -> " "
354
+ <> case symbolType of
355
+ Just s -> case s of
356
+ Only sym -> " (" <> sym <> " )"
357
+ AllOf dt -> " (" <> dt <> " (..))"
358
+ OneOf dt sym -> " (" <> dt <> " (" <> sym <> " ))"
359
+ Nothing -> " "
360
+
361
+ importStyleParam :: ImportStyle
362
+ importStyleParam = case symbolType of
363
+ Nothing -> Simple
364
+ Just k -> case termType importDiagnostic of
365
+ Hiding _ -> Complex (Hiding k)
366
+ Import _ -> Complex (Import k)
367
+
368
+ cmdParams = [toJSON (ImportParams (docId ^. J. uri) importStyleParam modName)]
255
369
256
370
257
371
-- | For a Diagnostic, get an associated function name.
258
372
-- If Ghc-Mod can not find any candidates, Nothing is returned.
259
- getImportables :: J. Diagnostic -> Maybe ( J. Diagnostic , T. Text )
373
+ getImportables :: J. Diagnostic -> Maybe ImportDiagnostic
260
374
getImportables diag@ (J. Diagnostic _ _ _ (Just " ghcmod" ) msg _) =
261
- (diag, ) <$> extractImportableTerm msg
375
+ uncurry ( ImportDiagnostic diag ) <$> extractImportableTerm msg
262
376
getImportables _ = Nothing
263
377
264
378
-- | Extract from an error message an appropriate term to search for.
265
379
-- This looks at the error message and tries to extract the expected
266
380
-- signature of an unknown function.
267
381
-- If this is not possible, Nothing is returned.
268
- extractImportableTerm :: T. Text -> Maybe T. Text
269
- extractImportableTerm dirtyMsg = T. strip <$> asum
270
- [ T. stripPrefix " Variable not in scope: " msg
271
- , T. init <$> T. stripPrefix " Not in scope: type constructor or class ‘" msg
272
- , T. stripPrefix " Data constructor not in scope: " msg
273
- ]
382
+ extractImportableTerm :: T. Text -> Maybe (T. Text , (SymbolImport SymbolType ) )
383
+ extractImportableTerm dirtyMsg =
384
+ let extractedTerm =
385
+ asum
386
+ [ (\ name -> (name, Import Symbol )) <$> T. stripPrefix " Variable not in scope: " importMsg
387
+ , (\ name -> (T. init name, Import Type )) <$> T. stripPrefix " Not in scope: type constructor or class ‘" importMsg
388
+ , (\ name -> (name, Import Constructor )) <$> T. stripPrefix " Data constructor not in scope: " importMsg
389
+ ]
390
+ in do
391
+ (n, s) <- extractedTerm
392
+ let n' = T. strip n
393
+ return (n', s)
274
394
where
275
- msg =
395
+ importMsg =
276
396
head
277
397
-- Get rid of the rename suggestion parts
278
398
$ T. splitOn " Perhaps you meant "
0 commit comments