1
1
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
2
2
-- SPDX-License-Identifier: Apache-2.0
3
3
{-# OPTIONS_GHC -Wno-dodgy-imports #-} -- GHC no longer exports def in GHC 8.6 and above
4
+ {-# LANGUAGE DeriveGeneric #-}
5
+ {-# LANGUAGE OverloadedStrings #-}
4
6
{-# LANGUAGE RecordWildCards #-}
5
- {-# LANGUAGE ViewPatterns #-}
6
7
{-# LANGUAGE TupleSections #-}
7
- {-# LANGUAGE OverloadedStrings #-}
8
+ {-# LANGUAGE TypeFamilies #-}
9
+ {-# LANGUAGE ViewPatterns #-}
8
10
9
11
module Main (main ) where
10
12
11
13
import Arguments
12
14
import Control.Concurrent.Extra
15
+ import Control.DeepSeq (NFData )
13
16
import Control.Exception
14
17
import Control.Monad.Extra
15
18
import Control.Monad.IO.Class
19
+ import Data.Binary (Binary )
16
20
import Data.Default
21
+ import Data.Dynamic (Typeable )
22
+ import qualified Data.HashSet as HashSet
23
+ import Data.Hashable (Hashable )
17
24
import Data.List.Extra
18
25
import qualified Data.Map.Strict as Map
19
26
import Data.Maybe
@@ -34,16 +41,21 @@ import Development.IDE.Types.Diagnostics
34
41
import Development.IDE.Types.Location
35
42
import Development.IDE.Types.Logger
36
43
import Development.IDE.Types.Options
37
- import Development.Shake (Action , action )
44
+ import Development.Shake (Action , RuleResult , Rules , action , doesFileExist , need )
38
45
import GHC hiding (def )
46
+ import GHC.Generics (Generic )
47
+ -- import qualified GHC.Paths
39
48
import HIE.Bios
40
- import Ide.Plugin.Formatter
49
+ import HIE.Bios.Cradle
50
+ import HIE.Bios.Types
51
+ import Ide.Plugin
41
52
import Ide.Plugin.Config
53
+ -- import Ide.Plugin.Formatter
42
54
import Language.Haskell.LSP.Messages
43
55
import Language.Haskell.LSP.Types (LspId (IdInt ))
44
56
import Linker
45
- import qualified Data.HashSet as HashSet
46
- import System.Directory.Extra as IO
57
+ import qualified System.Directory.Extra as IO
58
+ -- import System.Environment
47
59
import System.Exit
48
60
import System.FilePath
49
61
import System.IO
@@ -70,6 +82,7 @@ idePlugins includeExample
70
82
CodeAction. plugin <>
71
83
formatterPlugins [(" ormolu" , Ormolu. provider)
72
84
,(" floskell" , Floskell. provider)] <>
85
+ hoverPlugins [Example. hover, Example2. hover] <>
73
86
if includeExample then Example. plugin <> Example2. plugin
74
87
else mempty
75
88
@@ -89,9 +102,9 @@ main = do
89
102
let logger p = Logger $ \ pri msg -> when (pri >= p) $ withLock lock $
90
103
T. putStrLn $ T. pack (" [" ++ upper (show pri) ++ " ] " ) <> msg
91
104
92
- whenJust argsCwd setCurrentDirectory
105
+ whenJust argsCwd IO. setCurrentDirectory
93
106
94
- dir <- getCurrentDirectory
107
+ dir <- IO. getCurrentDirectory
95
108
96
109
let plugins = idePlugins argsExamplePlugin
97
110
@@ -102,22 +115,21 @@ main = do
102
115
runLanguageServer def (pluginHandler plugins) getInitialConfig getConfigFromNotification $ \ getLspId event vfs caps -> do
103
116
t <- t
104
117
hPutStrLn stderr $ " Started LSP server in " ++ showDuration t
105
- -- very important we only call loadSession once, and it's fast, so just do it before starting
106
- session <- loadSession dir
107
- let options = (defaultIdeOptions $ return session)
118
+ let options = (defaultIdeOptions $ loadSession dir)
108
119
{ optReportProgress = clientSupportsProgress caps
109
120
, optShakeProfiling = argsShakeProfiling
110
121
}
111
122
debouncer <- newAsyncDebouncer
112
- initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound ) debouncer options vfs
123
+ initialise caps (loadGhcSessionIO >> mainRule >> pluginRules plugins >> action kick)
124
+ getLspId event (logger minBound ) debouncer options vfs
113
125
else do
114
126
putStrLn $ " (haskell-language-server)Ghcide setup tester in " ++ dir ++ " ."
115
127
putStrLn " Report bugs at https://github.com/haskell/haskell-language-server/issues"
116
128
117
129
putStrLn $ " \n Step 1/6: Finding files to test in " ++ dir
118
130
files <- expandFiles (argFiles ++ [" ." | null argFiles])
119
131
-- LSP works with absolute file paths, so try and behave similarly
120
- files <- nubOrd <$> mapM canonicalizePath files
132
+ files <- nubOrd <$> mapM IO. canonicalizePath files
121
133
putStrLn $ " Found " ++ show (length files) ++ " files"
122
134
123
135
putStrLn " \n Step 2/6: Looking for hie.yaml files that control setup"
@@ -131,7 +143,8 @@ main = do
131
143
cradle <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle x
132
144
when (isNothing x) $ print cradle
133
145
putStrLn $ " \n Step 4/6, Cradle " ++ show i ++ " /" ++ show n ++ " : Loading GHC Session"
134
- cradleToSession cradle
146
+ opts <- getComponentOptions cradle
147
+ createSession opts
135
148
136
149
putStrLn " \n Step 5/6: Initializing the IDE"
137
150
vfs <- makeVFSHandle
@@ -144,7 +157,7 @@ main = do
144
157
let options =
145
158
(defaultIdeOptions $ return $ return . grab)
146
159
{ optShakeProfiling = argsShakeProfiling }
147
- ide <- initialise def mainRule (pure $ IdInt 0 ) (showEvent lock) (logger Info ) noopDebouncer options vfs
160
+ ide <- initialise def (loadGhcSessionIO >> mainRule) (pure $ IdInt 0 ) (showEvent lock) (logger Info ) noopDebouncer options vfs
148
161
149
162
putStrLn " \n Step 6/6: Type checking the files"
150
163
setFilesOfInterest ide $ HashSet. fromList $ map toNormalizedFilePath files
@@ -166,7 +179,7 @@ expandFiles = concatMapM $ \x -> do
166
179
let recurse " ." = True
167
180
recurse x | " ." `isPrefixOf` takeFileName x = False -- skip .git etc
168
181
recurse x = takeFileName x `notElem` [" dist" ," dist-newstyle" ] -- cabal directories
169
- files <- filter (\ x -> takeExtension x `elem` [" .hs" ," .lhs" ]) <$> listFilesInside (return . recurse) x
182
+ files <- filter (\ x -> takeExtension x `elem` [" .hs" ," .lhs" ]) <$> IO. listFilesInside (return . recurse) x
170
183
when (null files) $
171
184
fail $ " Couldn't find any .hs/.lhs files inside directory: " ++ x
172
185
return files
@@ -185,15 +198,42 @@ showEvent lock (EventFileDiagnostics (toNormalizedFilePath -> file) diags) =
185
198
showEvent lock e = withLock lock $ print e
186
199
187
200
188
- cradleToSession :: Cradle a -> IO HscEnvEq
189
- cradleToSession cradle = do
190
- cradleRes <- getCompilerOptions " " cradle
191
- opts <- case cradleRes of
201
+ -- Rule type for caching GHC sessions.
202
+ type instance RuleResult GetHscEnv = HscEnvEq
203
+
204
+ data GetHscEnv = GetHscEnv
205
+ { hscenvOptions :: [String ] -- componentOptions from hie-bios
206
+ , hscenvDependencies :: [FilePath ] -- componentDependencies from hie-bios
207
+ }
208
+ deriving (Eq , Show , Typeable , Generic )
209
+ instance Hashable GetHscEnv
210
+ instance NFData GetHscEnv
211
+ instance Binary GetHscEnv
212
+
213
+
214
+ loadGhcSessionIO :: Rules ()
215
+ loadGhcSessionIO =
216
+ -- This rule is for caching the GHC session. E.g., even when the cabal file
217
+ -- changed, if the resulting flags did not change, we would continue to use
218
+ -- the existing session.
219
+ defineNoFile $ \ (GetHscEnv opts deps) ->
220
+ liftIO $ createSession $ ComponentOptions opts deps
221
+
222
+
223
+ getComponentOptions :: Cradle a -> IO ComponentOptions
224
+ getComponentOptions cradle = do
225
+ let showLine s = putStrLn (" > " ++ s)
226
+ cradleRes <- runCradle (cradleOptsProg cradle) showLine " "
227
+ case cradleRes of
192
228
CradleSuccess r -> pure r
193
229
CradleFail err -> throwIO err
194
230
-- TODO Rather than failing here, we should ignore any files that use this cradle.
195
231
-- That will require some more changes.
196
232
CradleNone -> fail " 'none' cradle is not yet supported"
233
+
234
+
235
+ createSession :: ComponentOptions -> IO HscEnvEq
236
+ createSession opts = do
197
237
libdir <- getLibdir
198
238
env <- runGhc (Just libdir) $ do
199
239
_targets <- initSession opts
@@ -202,19 +242,34 @@ cradleToSession cradle = do
202
242
newHscEnvEq env
203
243
204
244
205
- loadSession :: FilePath -> IO (FilePath -> Action HscEnvEq )
206
- loadSession dir = do
245
+ cradleToSession :: Maybe FilePath -> Cradle a -> Action HscEnvEq
246
+ cradleToSession mbYaml cradle = do
247
+ cmpOpts <- liftIO $ getComponentOptions cradle
248
+ let opts = componentOptions cmpOpts
249
+ deps = componentDependencies cmpOpts
250
+ deps' = case mbYaml of
251
+ -- For direct cradles, the hie.yaml file itself must be watched.
252
+ Just yaml | isDirectCradle cradle -> yaml : deps
253
+ _ -> deps
254
+ existingDeps <- filterM doesFileExist deps'
255
+ need existingDeps
256
+ useNoFile_ $ GetHscEnv opts deps
257
+
258
+
259
+ loadSession :: FilePath -> Action (FilePath -> Action HscEnvEq )
260
+ loadSession dir = liftIO $ do
207
261
cradleLoc <- memoIO $ \ v -> do
208
262
res <- findCradle v
209
263
-- Sometimes we get C:, sometimes we get c:, and sometimes we get a relative path
210
264
-- try and normalise that
211
265
-- e.g. see https://github.com/digital-asset/ghcide/issues/126
212
- res' <- traverse makeAbsolute res
266
+ res' <- traverse IO. makeAbsolute res
213
267
return $ normalise <$> res'
214
- session <- memoIO $ \ file -> do
215
- c <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle file
216
- cradleToSession c
217
- return $ \ file -> liftIO $ session =<< cradleLoc file
268
+ let session :: Maybe FilePath -> Action HscEnvEq
269
+ session file = do
270
+ c <- liftIO $ maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle file
271
+ cradleToSession file c
272
+ return $ \ file -> session =<< liftIO (cradleLoc file)
218
273
219
274
220
275
-- | Memoize an IO function, with the characteristics:
0 commit comments