1
1
{-# LANGUAGE RecordWildCards #-}
2
+ {-# LANGUAGE OverloadedStrings #-}
3
+ {-# LANGUAGE LambdaCase #-}
4
+ {-# LANGUAGE NumericUnderscores #-}
2
5
-- | This module is based on the hie-wrapper.sh script in
3
6
-- https://github.com/alanz/vscode-hie-server
4
7
module Main where
5
8
6
9
import Control.Monad.Extra
10
+ import Control.Monad.IO.Class
11
+ import Control.Monad.Trans.Except
12
+ import qualified Data.Aeson as Aeson
13
+ import qualified Data.ByteString.Lazy as BSL
7
14
import Data.Default
8
15
import Data.Foldable
9
16
import Data.List
17
+ import Data.Maybe
18
+ import qualified Data.Text as T
19
+ import qualified Data.Text.IO as T
20
+ import qualified Data.Text.Lazy as TL
21
+ import qualified Data.Text.Lazy.Encoding as TL
10
22
import Data.Void
11
23
import qualified Development.IDE.Session as Session
12
24
import qualified HIE.Bios.Environment as HieBios
13
25
import HIE.Bios.Types
14
26
import Ide.Arguments
15
27
import Ide.Version
28
+ import qualified Language.LSP.Types as J
16
29
import System.Directory
17
30
import System.Environment
18
31
import System.Exit
19
32
import System.FilePath
20
33
import System.IO
21
34
import System.Info
22
35
import System.Process
36
+ import Control.Concurrent.Strict (threadDelay )
23
37
24
38
-- ---------------------------------------------------------------------
25
39
@@ -46,9 +60,17 @@ main = do
46
60
BiosMode PrintCradleType ->
47
61
print =<< findProjectCradle
48
62
49
- _ -> launchHaskellLanguageServer args
50
-
51
- launchHaskellLanguageServer :: Arguments -> IO ()
63
+ _ -> launchHaskellLanguageServer args >>= \ case
64
+ Right () -> pure ()
65
+ Left err -> do
66
+ T. hPutStrLn stderr " *** Startup ERROR"
67
+ T. hPutStrLn stderr (prettyError err NoShorten )
68
+ putWindowMessage J. MtError (prettyError err Shorten )
69
+ -- Wait for 45 seconds before shutdown, so we don't spam the same message
70
+ -- since some LSP clients attempt to re-launch the server.
71
+ threadDelay (45 * 1_000_000 )
72
+
73
+ launchHaskellLanguageServer :: Arguments -> IO (Either WrapperSetupError () )
52
74
launchHaskellLanguageServer parsedArgs = do
53
75
case parsedArgs of
54
76
Ghcide GhcideArguments {.. } -> whenJust argsCwd setCurrentDirectory
@@ -64,7 +86,10 @@ launchHaskellLanguageServer parsedArgs = do
64
86
65
87
case parsedArgs of
66
88
Ghcide GhcideArguments {.. } ->
67
- when argsProjectGhcVersion $ getRuntimeGhcVersion' cradle >>= putStrLn >> exitSuccess
89
+ when argsProjectGhcVersion $ do
90
+ runExceptT (getRuntimeGhcVersion' cradle) >>= \ case
91
+ Right ghcVersion -> putStrLn ghcVersion >> exitSuccess
92
+ Left err -> T. putStrLn (prettyError err NoShorten ) >> exitFailure
68
93
_ -> pure ()
69
94
70
95
progName <- getProgName
@@ -83,51 +108,53 @@ launchHaskellLanguageServer parsedArgs = do
83
108
hPutStrLn stderr " "
84
109
-- Get the ghc version -- this might fail!
85
110
hPutStrLn stderr " Consulting the cradle to get project GHC version..."
86
- ghcVersion <- getRuntimeGhcVersion' cradle
87
- hPutStrLn stderr $ " Project GHC version: " ++ ghcVersion
88
-
89
- let
90
- hlsBin = " haskell-language-server-" ++ ghcVersion
91
- candidates' = [hlsBin, " haskell-language-server" ]
92
- candidates = map (++ exeExtension) candidates'
93
-
94
- hPutStrLn stderr $ " haskell-language-server exe candidates: " ++ show candidates
95
-
96
- mexes <- traverse findExecutable candidates
97
-
98
- case asum mexes of
99
- Nothing -> hPutStrLn stderr $ " Cannot find any haskell-language-server exe, looked for: " ++ intercalate " , " candidates
100
- Just e -> do
101
- hPutStrLn stderr $ " Launching haskell-language-server exe at:" ++ e
102
- callProcess e args
103
-
104
- -- | Version of 'getRuntimeGhcVersion' that dies if we can't get it, and also
105
- -- checks to see if the tool is missing if it is one of
106
- getRuntimeGhcVersion' :: Show a => Cradle a -> IO String
111
+ runExceptT (getRuntimeGhcVersion' cradle) >>= \ case
112
+ Left err -> pure $ Left err
113
+ Right ghcVersion -> do
114
+ hPutStrLn stderr $ " Project GHC version: " ++ ghcVersion
115
+
116
+ let
117
+ hlsBin = " haskell-language-server-" ++ ghcVersion
118
+ candidates' = [hlsBin, " haskell-language-server" ]
119
+ candidates = map (++ exeExtension) candidates'
120
+
121
+ hPutStrLn stderr $ " haskell-language-server exe candidates: " ++ show candidates
122
+
123
+ mexes <- traverse findExecutable candidates
124
+
125
+ case asum mexes of
126
+ Nothing -> pure $ Left $ NoLanguageServer ghcVersion candidates
127
+ Just e -> do
128
+ hPutStrLn stderr $ " Launching haskell-language-server exe at:" ++ e
129
+ callProcess e args
130
+ pure $ Right ()
131
+
132
+ -- | Version of 'getRuntimeGhcVersion' that throws a 'WrapperSetupError' if we
133
+ -- can't get it, and also checks if run-time tool dependencies are missing.
134
+ getRuntimeGhcVersion' :: Cradle Void -> ExceptT WrapperSetupError IO String
107
135
getRuntimeGhcVersion' cradle = do
108
136
137
+ let cradleName = actionName (cradleOptsProg cradle)
109
138
-- See if the tool is installed
110
- case actionName (cradleOptsProg cradle) of
139
+ case cradleName of
111
140
Stack -> checkToolExists " stack"
112
141
Cabal -> checkToolExists " cabal"
113
142
Default -> checkToolExists " ghc"
114
143
Direct -> checkToolExists " ghc"
115
144
_ -> pure ()
116
145
117
- ghcVersionRes <- HieBios. getRuntimeGhcVersion cradle
146
+ ghcVersionRes <- liftIO $ HieBios. getRuntimeGhcVersion cradle
118
147
case ghcVersionRes of
119
148
CradleSuccess ver -> do
120
149
return ver
121
- CradleFail error -> die $ " Failed to get project GHC version: " ++ show error
122
- CradleNone -> die " Failed get project GHC version, since we have a none cradle "
150
+ CradleFail error -> throwE $ FailedToObtainGhcVersion cradleName error
151
+ CradleNone -> throwE $ NoneCradleGhcVersion cradleName
123
152
where
124
153
checkToolExists exe = do
125
- exists <- findExecutable exe
154
+ exists <- liftIO $ findExecutable exe
126
155
case exists of
127
156
Just _ -> pure ()
128
- Nothing ->
129
- die $ " Cradle requires " ++ exe ++ " but couldn't find it" ++ " \n "
130
- ++ show cradle
157
+ Nothing -> throwE $ ToolRequirementMissing exe (actionName (cradleOptsProg cradle))
131
158
132
159
findProjectCradle :: IO (Cradle Void )
133
160
findProjectCradle = do
@@ -142,3 +169,69 @@ findProjectCradle = do
142
169
Nothing -> hPutStrLn stderr " No 'hie.yaml' found. Try to discover the project type!"
143
170
144
171
Session. loadCradle def hieYaml d
172
+
173
+ data WrapperSetupError
174
+ = FailedToObtainGhcVersion (ActionName Void ) CradleError
175
+ | NoneCradleGhcVersion (ActionName Void )
176
+ | NoLanguageServer String [FilePath ]
177
+ | ToolRequirementMissing String (ActionName Void )
178
+ deriving (Show )
179
+
180
+ data Shorten = Shorten | NoShorten
181
+
182
+ -- | Pretty error message displayable to the future.
183
+ -- Extra argument 'Shorten' can be used to shorten error message.
184
+ -- Reduces usefulness, but allows us to show the error message via LSP
185
+ -- as LSP doesn't allow any newlines and makes it really hard to read
186
+ -- the message otherwise.
187
+ prettyError :: WrapperSetupError -> Shorten -> T. Text
188
+ prettyError (FailedToObtainGhcVersion name crdlError) shorten =
189
+ " Failed to find the GHC version of this " <> T. pack (show name) <> " project." <>
190
+ case shorten of
191
+ Shorten ->
192
+ " \n " <> T. pack (fromMaybe " " . listToMaybe $ cradleErrorStderr crdlError)
193
+ NoShorten ->
194
+ " \n " <> T. pack (intercalate " \r\n " (cradleErrorStderr crdlError))
195
+ prettyError (NoneCradleGhcVersion name) _ =
196
+ " Failed to get the GHC version of the " <> T. pack (show name) <>
197
+ " project, since we have a none cradle"
198
+ prettyError (NoLanguageServer ghcVersion candidates) _ =
199
+ " Failed to find a HLS version for GHC " <> T. pack ghcVersion <>
200
+ " \n Executable names we failed to find: " <> T. pack (intercalate " ," candidates)
201
+ prettyError (ToolRequirementMissing toolExe name) _ =
202
+ " This is a " <> T. pack (show name) <> " Project, but we failed to find \" " <>
203
+ T. pack toolExe <> " \" on the $PATH"
204
+
205
+ -- LSP Helper functions
206
+ -- ~~~~~~~~~~~~~~~~~~~~
207
+ --
208
+ -- To send lsp messages without a full-fledged LSP server.
209
+ -- Should be used to indicate errors to the user.
210
+
211
+ putWindowMessage :: J. MessageType -> T. Text -> IO ()
212
+ putWindowMessage mt message = do
213
+ let bsMessage = toLspMessage windowMsg
214
+ BSL. hPut stdout bsMessage
215
+ hFlush stdout
216
+ where
217
+ windowMsg = windowMessage mt message
218
+
219
+ windowMessage :: J. MessageType -> T. Text -> J. FromServerMessage
220
+ windowMessage mt message = J. FromServerMess J. SWindowShowMessage $
221
+ J. NotificationMessage " 2.0" J. SWindowShowMessage
222
+ (J. ShowMessageParams mt message)
223
+
224
+
225
+ -- | Given a server message, attach the relevant header information.
226
+ toLspMessage :: J. FromServerMessage -> BSL. ByteString
227
+ toLspMessage msg =
228
+ BSL. concat
229
+ [ TL. encodeUtf8 $ TL. pack $ " Content-Length: " ++ show (BSL. length str)
230
+ , _TWO_CRLF
231
+ , str ]
232
+ where
233
+ str = Aeson. encode msg
234
+
235
+ -- | Constant taken from the 'lsp' package.
236
+ _TWO_CRLF :: BSL. ByteString
237
+ _TWO_CRLF = " \r\n\r\n "
0 commit comments