11{-# LANGUAGE OverloadedStrings #-}
22{-# LANGUAGE DeriveGeneric #-}
33{-# LANGUAGE DeriveAnyClass #-}
4- {-# LANGUAGE TupleSections #-}
54{-# LANGUAGE LambdaCase #-}
65module Haskell.Ide.Engine.Plugin.HsImport where
76
87import Control.Lens.Operators
98import Control.Monad.IO.Class
109import Control.Monad
1110import Data.Aeson
12- import Data.Bitraversable
13- import Data.Bifunctor
1411import Data.Foldable
1512import Data.Maybe
1613import Data.Monoid ( (<>) )
1714import qualified Data.Text as T
1815import qualified Data.Text.IO as T
1916import qualified GHC.Generics as Generics
2017import qualified GhcModCore as GM ( mkRevRedirMapFunc , withMappedFile )
21- import HsImport
18+ import qualified HsImport
2219import Haskell.Ide.Engine.Config
2320import Haskell.Ide.Engine.MonadTypes
2421import qualified Haskell.Ide.Engine.Support.HieExtras as Hie
@@ -29,6 +26,7 @@ import qualified Haskell.Ide.Engine.Plugin.Hoogle
2926 as Hoogle
3027import System.Directory
3128import System.IO
29+ import qualified Safe as S
3230
3331hsimportDescriptor :: PluginId -> PluginDescriptor
3432hsimportDescriptor plId = PluginDescriptor
@@ -43,28 +41,70 @@ hsimportDescriptor plId = PluginDescriptor
4341 , pluginFormattingProvider = Nothing
4442 }
4543
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+
4686-- | Import Parameters for Modules.
4787-- Can be used to import every symbol from a module,
4888-- or to import only a specific function from a module.
4989data 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.
5393 }
5494 deriving (Show , Eq , Generics.Generic , ToJSON , FromJSON )
5595
5696importCmd :: 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
5999
60100-- | Import the given module for the given file.
61101-- May take an explicit function name to perform an import-list import.
62102-- Multiple import-list imports will result in merged imports,
63103-- e.g. two consecutive imports for the same module will result in a single
64104-- import line.
65105importModule
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 =
68108 pluginGetFile " hsimport cmd: " uri $ \ origInput -> do
69109 shouldFormat <- formatOnImportOn <$> getConfig
70110 fileMap <- GM. mkRevRedirMapFunc
@@ -73,13 +113,9 @@ importModule uri importList modName =
73113 tmpDir <- liftIO getTemporaryDirectory
74114 (output, outputH) <- liftIO $ openTempFile tmpDir " hsimportOutput"
75115 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
81117 -- 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
83119 case maybeErr of
84120 Just err -> do
85121 liftIO $ removeFile output
@@ -153,6 +189,29 @@ importModule uri importList modName =
153189 $ IdeResultOk (J. WorkspaceEdit newChanges newDocChanges)
154190 else return $ IdeResultOk (J. WorkspaceEdit mChanges mDocChanges)
155191
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+
156215-- | Search style for Hoogle.
157216-- Can be used to look either for the exact term,
158217-- only the exact name or a relaxed form of the term.
@@ -188,28 +247,23 @@ codeActionProvider plId docId _ context = do
188247 --
189248 -- Result may produce several import actions, or none.
190249 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
202256
203257 -- | Apply the search style to given term.
204258 -- Can be used to look for a term that matches exactly the search term,
205259 -- or one that matches only the exact name.
206260 -- At last, a custom relaxation function can be passed for more control.
207261 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
211265 (x : _) -> " is:exact " <> x
212- applySearchStyle (Relax relax) term = relax term
266+ applySearchStyle (Relax relax) termName = relax termName
213267
214268 -- | Turn a search term with function name into Import Actions.
215269 -- Function name may be of only the exact phrase to import.
@@ -224,55 +278,121 @@ codeActionProvider plId docId _ context = do
224278 -- no import list can be offered, since the function name
225279 -- may be not the one we expect.
226280 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
234335
235- concatTerms :: (a , [b ]) -> [(a , b )]
236- concatTerms (a, b) = zip (repeat a) b
237336
238337 -- TODO: Check if package is already installed
239338 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
242341 cmd <- mkLspCommand plId " import" title (Just cmdParams)
243342 return (Just (codeAction cmd))
244343 where
245344 codeAction cmd = J. CodeAction title
246345 (Just J. CodeActionQuickFix )
247- (Just (J. List [diag ]))
346+ (Just (J. List [diagnostic importDiagnostic ]))
248347 Nothing
249348 (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)]
255369
256370
257371 -- | For a Diagnostic, get an associated function name.
258372 -- 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
260374 getImportables diag@ (J. Diagnostic _ _ _ (Just " ghcmod" ) msg _) =
261- (diag, ) <$> extractImportableTerm msg
375+ uncurry ( ImportDiagnostic diag ) <$> extractImportableTerm msg
262376 getImportables _ = Nothing
263377
264378-- | Extract from an error message an appropriate term to search for.
265379-- This looks at the error message and tries to extract the expected
266380-- signature of an unknown function.
267381-- 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)
274394 where
275- msg =
395+ importMsg =
276396 head
277397 -- Get rid of the rename suggestion parts
278398 $ T. splitOn " Perhaps you meant "
0 commit comments