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
@@ -14,11 +16,14 @@ import Control.Exception
14
16
import Control.Monad.Extra
15
17
import Control.Monad.IO.Class
16
18
import Data.Default
19
+ import qualified Data.HashSet as HashSet
17
20
import Data.List.Extra
18
21
import qualified Data.Map.Strict as Map
19
22
import Data.Maybe
20
23
import qualified Data.Text as T
21
24
import qualified Data.Text.IO as T
25
+ -- import Data.Version
26
+ -- import Development.GitRev
22
27
import Development.IDE.Core.Debouncer
23
28
import Development.IDE.Core.FileStore
24
29
import Development.IDE.Core.OfInterest
@@ -34,44 +39,83 @@ import Development.IDE.Types.Diagnostics
34
39
import Development.IDE.Types.Location
35
40
import Development.IDE.Types.Logger
36
41
import Development.IDE.Types.Options
37
- import Development.Shake (Action , action )
38
- import GHC hiding (def )
42
+ import Development.Shake (Action , Rules , action )
39
43
import HIE.Bios
40
- import Ide.Plugin.Formatter
44
+ import qualified Language.Haskell.LSP.Core as LSP
45
+ import Ide.Logger
46
+ import Ide.Plugin
41
47
import Ide.Plugin.Config
42
48
import Language.Haskell.LSP.Messages
43
49
import Language.Haskell.LSP.Types (LspId (IdInt ))
44
- import Linker
45
- import qualified Data.HashSet as HashSet
46
- import System.Directory.Extra as IO
50
+ import RuleTypes
51
+ import Rules
52
+ import qualified System.Directory.Extra as IO
53
+ -- import System.Environment
47
54
import System.Exit
48
55
import System.FilePath
49
56
import System.IO
57
+ import System.Log.Logger as L
50
58
import System.Time.Extra
51
59
52
60
-- ---------------------------------------------------------------------
53
61
54
62
import Development.IDE.Plugin.CodeAction as CodeAction
55
63
import Development.IDE.Plugin.Completions as Completions
56
64
import Ide.Plugin.Example as Example
65
+ import Ide.Plugin.Example2 as Example2
57
66
import Ide.Plugin.Floskell as Floskell
58
67
import Ide.Plugin.Ormolu as Ormolu
68
+ import Ide.Plugin.Pragmas as Pragmas
59
69
60
70
-- ---------------------------------------------------------------------
61
71
62
- -- The plugins configured for use in this instance of the language
72
+ -- | The plugins configured for use in this instance of the language
63
73
-- server.
64
74
-- These can be freely added or removed to tailor the available
65
75
-- features of the server.
66
- idePlugins :: Bool -> Plugin Config
67
- idePlugins includeExample
68
- = Completions. plugin <>
69
- CodeAction. plugin <>
70
- formatterPlugins [(" ormolu" , Ormolu. provider)
71
- ,(" floskell" , Floskell. provider)] <>
72
- if includeExample then Example. plugin else mempty
76
+ idePlugins :: T. Text -> Bool -> (Plugin Config , [T. Text ])
77
+ idePlugins pid includeExamples
78
+ = (asGhcIdePlugin ps, allLspCmdIds' pid ps)
79
+ where
80
+ ps = pluginDescToIdePlugins allPlugins
81
+ allPlugins = if includeExamples
82
+ then basePlugins ++ examplePlugins
83
+ else basePlugins
84
+ basePlugins =
85
+ [
86
+ -- applyRefactDescriptor "applyrefact"
87
+ -- , brittanyDescriptor "brittany"
88
+ -- , haddockDescriptor "haddock"
89
+ -- -- , hareDescriptor "hare"
90
+ -- , hsimportDescriptor "hsimport"
91
+ -- , liquidDescriptor "liquid"
92
+ -- , packageDescriptor "package"
93
+ Pragmas. descriptor " pragmas"
94
+ , Floskell. descriptor " floskell"
95
+ -- , genericDescriptor "generic"
96
+ -- , ghcmodDescriptor "ghcmod"
97
+ , Ormolu. descriptor " ormolu"
98
+ ]
99
+ examplePlugins =
100
+ [Example. descriptor " eg"
101
+ ,Example2. descriptor " eg2"
102
+ -- ,hfaAlignDescriptor "hfaa"
103
+ ]
104
+
73
105
74
106
-- ---------------------------------------------------------------------
107
+ -- Prefix for the cache path
108
+ {-
109
+ cacheDir :: String
110
+ cacheDir = "ghcide"
111
+
112
+ getCacheDir :: [String] -> IO FilePath
113
+ getCacheDir opts = IO.getXdgDirectory IO.XdgCache (cacheDir </> opts_hash)
114
+ where
115
+ -- Create a unique folder per set of different GHC options, assuming that each different set of
116
+ -- GHC options will create incompatible interface files.
117
+ opts_hash = B.unpack $ encode $ H.finalize $ H.updates H.init (map B.pack opts)
118
+ -}
75
119
76
120
main :: IO ()
77
121
main = do
@@ -82,40 +126,57 @@ main = do
82
126
if argsVersion then ghcideVersion >>= putStrLn >> exitSuccess
83
127
else hPutStrLn stderr {- see WARNING above -} =<< ghcideVersion
84
128
129
+ -- LSP.setupLogger (optLogFile opts) ["hie", "hie-bios"]
130
+ -- $ if optDebugOn opts then L.DEBUG else L.INFO
131
+ LSP. setupLogger argsLogFile [" hie" , " hie-bios" ]
132
+ $ if argsDebugOn then L. DEBUG else L. INFO
133
+
85
134
-- lock to avoid overlapping output on stdout
86
135
lock <- newLock
87
136
let logger p = Logger $ \ pri msg -> when (pri >= p) $ withLock lock $
88
137
T. putStrLn $ T. pack (" [" ++ upper (show pri) ++ " ] " ) <> msg
89
138
90
- whenJust argsCwd setCurrentDirectory
139
+ whenJust argsCwd IO. setCurrentDirectory
91
140
92
- dir <- getCurrentDirectory
141
+ dir <- IO. getCurrentDirectory
93
142
94
- let plugins = idePlugins argsExamplePlugin
143
+ pid <- getPid
144
+ let
145
+ -- (ps, commandIds) = idePlugins pid argsExamplePlugin
146
+ (ps, commandIds) = idePlugins pid True
147
+ plugins = Completions. plugin <> CodeAction. plugin <>
148
+ ps
149
+ options = def { LSP. executeCommandCommands = Just commandIds
150
+ , LSP. completionTriggerCharacters = Just " ."
151
+ }
95
152
96
153
if argLSP then do
97
154
t <- offsetTime
98
155
hPutStrLn stderr " Starting (haskell-language-server)LSP server..."
99
156
hPutStrLn stderr " If you are seeing this in a terminal, you probably should have run ghcide WITHOUT the --lsp option!"
100
- runLanguageServer def (pluginHandler plugins) getInitialConfig getConfigFromNotification $ \ getLspId event vfs caps -> do
157
+ runLanguageServer options (pluginHandler plugins) getInitialConfig getConfigFromNotification $ \ getLspId event vfs caps -> do
101
158
t <- t
102
159
hPutStrLn stderr $ " Started LSP server in " ++ showDuration t
103
- -- very important we only call loadSession once, and it's fast, so just do it before starting
104
- session <- loadSession dir
105
- let options = (defaultIdeOptions $ return session)
160
+ let options = (defaultIdeOptions $ loadSession dir)
106
161
{ optReportProgress = clientSupportsProgress caps
107
162
, optShakeProfiling = argsShakeProfiling
163
+ , optTesting = argsTesting
108
164
}
109
165
debouncer <- newAsyncDebouncer
110
- initialise caps (mainRule >> pluginRules plugins >> action kick) getLspId event (logger minBound ) debouncer options vfs
166
+ initialise caps (cradleRules >> mainRule >> pluginRules plugins >> action kick)
167
+ getLspId event hlsLogger debouncer options vfs
111
168
else do
169
+ -- GHC produces messages with UTF8 in them, so make sure the terminal doesn't error
170
+ hSetEncoding stdout utf8
171
+ hSetEncoding stderr utf8
172
+
112
173
putStrLn $ " (haskell-language-server)Ghcide setup tester in " ++ dir ++ " ."
113
174
putStrLn " Report bugs at https://github.com/haskell/haskell-language-server/issues"
114
175
115
176
putStrLn $ " \n Step 1/6: Finding files to test in " ++ dir
116
177
files <- expandFiles (argFiles ++ [" ." | null argFiles])
117
178
-- LSP works with absolute file paths, so try and behave similarly
118
- files <- nubOrd <$> mapM canonicalizePath files
179
+ files <- nubOrd <$> mapM IO. canonicalizePath files
119
180
putStrLn $ " Found " ++ show (length files) ++ " files"
120
181
121
182
putStrLn " \n Step 2/6: Looking for hie.yaml files that control setup"
@@ -129,7 +190,8 @@ main = do
129
190
cradle <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle x
130
191
when (isNothing x) $ print cradle
131
192
putStrLn $ " \n Step 4/6, Cradle " ++ show i ++ " /" ++ show n ++ " : Loading GHC Session"
132
- cradleToSession cradle
193
+ opts <- getComponentOptions cradle
194
+ createSession opts
133
195
134
196
putStrLn " \n Step 5/6: Initializing the IDE"
135
197
vfs <- makeVFSHandle
@@ -142,7 +204,7 @@ main = do
142
204
let options =
143
205
(defaultIdeOptions $ return $ return . grab)
144
206
{ optShakeProfiling = argsShakeProfiling }
145
- ide <- initialise def mainRule (pure $ IdInt 0 ) (showEvent lock) (logger Info ) noopDebouncer options vfs
207
+ ide <- initialise def (cradleRules >> mainRule) (pure $ IdInt 0 ) (showEvent lock) (logger Info ) noopDebouncer options vfs
146
208
147
209
putStrLn " \n Step 6/6: Type checking the files"
148
210
setFilesOfInterest ide $ HashSet. fromList $ map toNormalizedFilePath files
@@ -156,6 +218,10 @@ main = do
156
218
157
219
unless (null failed) exitFailure
158
220
221
+ cradleRules :: Rules ()
222
+ cradleRules = do
223
+ loadGhcSession
224
+ cradleToSession
159
225
160
226
expandFiles :: [FilePath ] -> IO [FilePath ]
161
227
expandFiles = concatMapM $ \ x -> do
@@ -164,7 +230,7 @@ expandFiles = concatMapM $ \x -> do
164
230
let recurse " ." = True
165
231
recurse x | " ." `isPrefixOf` takeFileName x = False -- skip .git etc
166
232
recurse x = takeFileName x `notElem` [" dist" ," dist-newstyle" ] -- cabal directories
167
- files <- filter (\ x -> takeExtension x `elem` [" .hs" ," .lhs" ]) <$> listFilesInside (return . recurse) x
233
+ files <- filter (\ x -> takeExtension x `elem` [" .hs" ," .lhs" ]) <$> IO. listFilesInside (return . recurse) x
168
234
when (null files) $
169
235
fail $ " Couldn't find any .hs/.lhs files inside directory: " ++ x
170
236
return files
@@ -182,37 +248,21 @@ showEvent lock (EventFileDiagnostics (toNormalizedFilePath -> file) diags) =
182
248
withLock lock $ T. putStrLn $ showDiagnosticsColored $ map (file,ShowDiag ,) diags
183
249
showEvent lock e = withLock lock $ print e
184
250
185
-
186
- cradleToSession :: Cradle a -> IO HscEnvEq
187
- cradleToSession cradle = do
188
- cradleRes <- getCompilerOptions " " cradle
189
- opts <- case cradleRes of
190
- CradleSuccess r -> pure r
191
- CradleFail err -> throwIO err
192
- -- TODO Rather than failing here, we should ignore any files that use this cradle.
193
- -- That will require some more changes.
194
- CradleNone -> fail " 'none' cradle is not yet supported"
195
- libdir <- getLibdir
196
- env <- runGhc (Just libdir) $ do
197
- _targets <- initSession opts
198
- getSession
199
- initDynLinker env
200
- newHscEnvEq env
201
-
202
-
203
- loadSession :: FilePath -> IO (FilePath -> Action HscEnvEq )
204
- loadSession dir = do
251
+ loadSession :: FilePath -> Action (FilePath -> Action HscEnvEq )
252
+ loadSession dir = liftIO $ do
205
253
cradleLoc <- memoIO $ \ v -> do
206
254
res <- findCradle v
207
255
-- Sometimes we get C:, sometimes we get c:, and sometimes we get a relative path
208
256
-- try and normalise that
209
257
-- e.g. see https://github.com/digital-asset/ghcide/issues/126
210
- res' <- traverse makeAbsolute res
258
+ res' <- traverse IO. makeAbsolute res
211
259
return $ normalise <$> res'
212
- session <- memoIO $ \ file -> do
213
- c <- maybe (loadImplicitCradle $ addTrailingPathSeparator dir) loadCradle file
214
- cradleToSession c
215
- return $ \ file -> liftIO $ session =<< cradleLoc file
260
+ let session :: Maybe FilePath -> Action HscEnvEq
261
+ session file = do
262
+ -- In the absence of a cradle file, just pass the directory from where to calculate an implicit cradle
263
+ let cradle = toNormalizedFilePath $ fromMaybe dir file
264
+ use_ LoadCradle cradle
265
+ return $ \ file -> session =<< liftIO (cradleLoc file)
216
266
217
267
218
268
-- | Memoize an IO function, with the characteristics:
0 commit comments