Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Commit 3350686

Browse files
committed
Clone HsImport API for more fine grained control
Enables to use imports of constructors
1 parent abdb097 commit 3350686

File tree

2 files changed

+180
-60
lines changed

2 files changed

+180
-60
lines changed

src/Haskell/Ide/Engine/Plugin/Hoogle.hs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ hoogleDescriptor plId = PluginDescriptor
4444

4545
-- ---------------------------------------------------------------------
4646

47-
data HoogleError
47+
data HoogleError
4848
= NoDb
4949
| DbFail T.Text
50-
| NoResults
50+
| NoResults
5151
deriving (Eq,Ord,Show)
5252

5353
newtype HoogleDb = HoogleDb (Maybe FilePath)

src/Haskell/Ide/Engine/Plugin/HsImport.hs

Lines changed: 178 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,21 @@
11
{-# LANGUAGE OverloadedStrings #-}
22
{-# LANGUAGE DeriveGeneric #-}
33
{-# LANGUAGE DeriveAnyClass #-}
4-
{-# LANGUAGE TupleSections #-}
54
{-# LANGUAGE LambdaCase #-}
65
module Haskell.Ide.Engine.Plugin.HsImport where
76

87
import Control.Lens.Operators
98
import Control.Monad.IO.Class
109
import Control.Monad
1110
import Data.Aeson
12-
import Data.Bitraversable
13-
import Data.Bifunctor
1411
import Data.Foldable
1512
import Data.Maybe
1613
import Data.Monoid ( (<>) )
1714
import qualified Data.Text as T
1815
import qualified Data.Text.IO as T
1916
import qualified GHC.Generics as Generics
2017
import qualified GhcModCore as GM ( mkRevRedirMapFunc, withMappedFile )
21-
import HsImport
18+
import qualified HsImport
2219
import Haskell.Ide.Engine.Config
2320
import Haskell.Ide.Engine.MonadTypes
2421
import qualified Haskell.Ide.Engine.Support.HieExtras as Hie
@@ -29,6 +26,7 @@ import qualified Haskell.Ide.Engine.Plugin.Hoogle
2926
as Hoogle
3027
import System.Directory
3128
import System.IO
29+
import qualified Safe as S
3230

3331
hsimportDescriptor :: PluginId -> PluginDescriptor
3432
hsimportDescriptor 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.
4989
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.
5393
}
5494
deriving (Show, Eq, Generics.Generic, ToJSON, FromJSON)
5595

5696
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
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.
65105
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 =
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

Comments
 (0)