Skip to content

Commit 6fdd7d4

Browse files
committed
import lens plugin
1 parent 0b12fcb commit 6fdd7d4

File tree

3 files changed

+127
-0
lines changed

3 files changed

+127
-0
lines changed

exe/Main.hs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ import Ide.Plugin.Example2 as Example2
6969
import Ide.Plugin.GhcIde as GhcIde
7070
import Ide.Plugin.Floskell as Floskell
7171
import Ide.Plugin.Fourmolu as Fourmolu
72+
import Ide.Plugin.ImportLens as ImportLens
7273
import Ide.Plugin.Ormolu as Ormolu
7374
import Ide.Plugin.StylishHaskell as StylishHaskell
7475
import Ide.Plugin.Retrie as Retrie
@@ -114,6 +115,7 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins
114115
, Brittany.descriptor "brittany"
115116
#endif
116117
, Eval.descriptor "eval"
118+
, ImportLens.descriptor "importLens"
117119
]
118120
examplePlugins =
119121
[Example.descriptor "eg"

haskell-language-server.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ library
4747
Ide.Plugin.Example2
4848
Ide.Plugin.Fourmolu
4949
Ide.Plugin.GhcIde
50+
Ide.Plugin.ImportLens
5051
Ide.Plugin.Ormolu
5152
Ide.Plugin.Pragmas
5253
Ide.Plugin.Retrie

src/Ide/Plugin/ImportLens.hs

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{-# LANGUAGE DeriveAnyClass #-}
2+
{-# LANGUAGE DerivingStrategies #-}
3+
{-# LANGUAGE DeriveGeneric #-}
4+
{-# LANGUAGE DuplicateRecordFields #-}
5+
{-# LANGUAGE LambdaCase #-}
6+
{-# LANGUAGE OverloadedStrings #-}
7+
{-# LANGUAGE ScopedTypeVariables #-}
8+
{-# LANGUAGE NamedFieldPuns #-}
9+
{-# LANGUAGE RecordWildCards #-}
10+
module Ide.Plugin.ImportLens (descriptor) where
11+
import Control.Monad (forM)
12+
import Data.Aeson (ToJSON)
13+
import Data.Aeson (Value(Null))
14+
import Data.Aeson.Types (FromJSON)
15+
import Data.IORef (readIORef)
16+
import Data.Map (Map)
17+
import qualified Data.Map.Strict as Map
18+
import Data.Maybe (fromMaybe, catMaybes)
19+
import qualified Data.Text as T
20+
import Development.IDE
21+
import Development.IDE.GHC.Compat
22+
import GHC.Generics (Generic)
23+
import Ide.Plugin
24+
import Ide.Types
25+
import Language.Haskell.LSP.Types
26+
import RnNames (getMinimalImports, findImportUsage)
27+
import TcRnMonad (initTcWithGbl)
28+
import TcRnTypes (TcGblEnv(tcg_used_gres))
29+
import PrelNames (pRELUDE)
30+
import Data.Aeson (ToJSON(toJSON))
31+
import qualified Data.HashMap.Strict as HashMap
32+
33+
importCommandId :: CommandId
34+
importCommandId = "ImportLensCommand"
35+
36+
descriptor :: PluginId -> PluginDescriptor
37+
descriptor plId = (defaultPluginDescriptor plId) {
38+
pluginCodeLensProvider = Just provider,
39+
pluginCommands = [ importLensCommand ]
40+
}
41+
42+
importLensCommand :: PluginCommand
43+
importLensCommand =
44+
PluginCommand importCommandId "Explicit import command" runImportCommand
45+
46+
data ImportCommandParams = ImportCommandParams WorkspaceEdit
47+
deriving Generic
48+
deriving anyclass (FromJSON, ToJSON)
49+
50+
runImportCommand :: CommandFunction ImportCommandParams
51+
runImportCommand _lspFuncs _state (ImportCommandParams edit) = do
52+
return (Right Null, Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams edit))
53+
54+
-- For every implicit import statement,
55+
-- return a code lens of the corresponding explicit import
56+
-- Example. For the module below:
57+
--
58+
-- > import Data.List
59+
-- >
60+
-- > f = intercalate " " . sortBy length
61+
--
62+
-- the provider should produce one code lens:
63+
--
64+
-- > import Data.List (intercalate, sortBy)
65+
66+
provider :: CodeLensProvider
67+
provider _lspFuncs state pId CodeLensParams{..}
68+
| TextDocumentIdentifier{_uri} <- _textDocument
69+
, Just nfp <- uriToNormalizedFilePath $ toNormalizedUri _uri
70+
= do
71+
Just (tmr, _) <- runIde state $ useWithStaleFast TypeCheck nfp
72+
hsc <- hscEnv <$> runAction "importLens" state (use_ GhcSessionDeps nfp)
73+
(imports, mbMinImports) <- extractMinimalImports hsc (tmrModule tmr)
74+
75+
case mbMinImports of
76+
Just minImports -> do
77+
let minImportsMap =
78+
Map.fromList [ (srcSpanStart l, i) | L l i <- minImports ]
79+
commands <- forM imports $ generateLens pId _uri minImportsMap
80+
return $ Right (List $ catMaybes commands)
81+
_ ->
82+
return $ Right (List [])
83+
84+
| otherwise
85+
= return $ Right (List [])
86+
87+
extractMinimalImports :: HscEnv -> TypecheckedModule -> IO ([LImportDecl GhcRn], Maybe [LImportDecl GhcRn])
88+
extractMinimalImports hsc TypecheckedModule{..} = do
89+
let (tcEnv,_) = tm_internals_
90+
Just (_, imports, _, _) = tm_renamed_source
91+
ParsedModule{ pm_parsed_source = L loc _} = tm_parsed_module
92+
93+
gblElts <- readIORef (tcg_used_gres tcEnv)
94+
let usage = findImportUsage imports gblElts
95+
span = fromMaybe (error "expected real") $ realSpan loc
96+
(_, minimalImports) <- initTcWithGbl hsc tcEnv span $ getMinimalImports usage
97+
return (imports, minimalImports)
98+
99+
generateLens :: PluginId -> Uri -> Map SrcLoc (ImportDecl GhcRn) -> LImportDecl GhcRn -> IO (Maybe CodeLens)
100+
generateLens pId uri minImports (L src imp)
101+
| ImportDecl{ideclHiding = Just (False,_)} <- imp
102+
= return Nothing
103+
| RealSrcSpan l <- src
104+
, Just explicit <- Map.lookup (srcSpanStart src) minImports
105+
, L _ mn <- ideclName imp
106+
, mn /= moduleName pRELUDE
107+
= do
108+
let title = T.pack $ prettyPrint explicit
109+
commandArgs = Nothing
110+
c <- mkLspCommand pId importCommandId title commandArgs
111+
let _range :: Range = realSrcSpanToRange l
112+
_xdata = Nothing
113+
edit = WorkspaceEdit (Just editsMap) Nothing
114+
editsMap = HashMap.fromList [(uri, List [importEdit])]
115+
importEdit = TextEdit _range title
116+
args = ImportCommandParams edit
117+
_arguments = Just (List [toJSON args])
118+
_command = Just (c :: Command){_arguments}
119+
return $ Just CodeLens{..}
120+
| otherwise
121+
= return Nothing
122+
123+
runIde :: IdeState -> IdeAction a -> IO a
124+
runIde state = runIdeAction "importLens" (shakeExtras state)

0 commit comments

Comments
 (0)