1
+ {-# LANGUAGE DerivingStrategies #-}
1
2
{-# LANGUAGE OverloadedStrings #-}
3
+ {-# LANGUAGE RecordWildCards #-}
2
4
3
5
module Language.LSP.Server.Control (
4
6
-- * Running
5
- runServer ,
6
7
runServerWith ,
7
- runServerWithHandles ,
8
+ runServerWithConfig ,
9
+ ServerConfig (.. ),
8
10
LspServerLog (.. ),
11
+
12
+ -- ** Using standard 'IO' 'Handle's
13
+ runServer ,
14
+
15
+ -- ** Using 'Handle's
16
+ runServerWithHandles ,
17
+ prependHeader ,
18
+ parseHeaders ,
19
+
20
+ -- ** Using websockets
21
+ WebsocketConfig (.. ),
22
+ withWebsocket ,
23
+ withWebsocketRunServer ,
9
24
) where
10
25
11
26
import Colog.Core (LogAction (.. ), Severity (.. ), WithSeverity (.. ), (<&) )
12
27
import Colog.Core qualified as L
13
28
import Control.Applicative ((<|>) )
14
29
import Control.Concurrent
30
+ import Control.Concurrent.Async
15
31
import Control.Concurrent.STM.TChan
32
+ import Control.Exception (finally )
16
33
import Control.Monad
17
34
import Control.Monad.IO.Class
18
35
import Control.Monad.STM
@@ -31,6 +48,7 @@ import Language.LSP.Protocol.Message
31
48
import Language.LSP.Server.Core
32
49
import Language.LSP.Server.Processing qualified as Processing
33
50
import Language.LSP.VFS
51
+ import Network.WebSockets qualified as WS
34
52
import Prettyprinter
35
53
import System.IO
36
54
@@ -42,6 +60,7 @@ data LspServerLog
42
60
| Starting
43
61
| ParsedMsg T. Text
44
62
| SendMsg TL. Text
63
+ | WebsocketLog WebsocketLog
45
64
deriving (Show )
46
65
47
66
instance Pretty LspServerLog where
@@ -60,6 +79,7 @@ instance Pretty LspServerLog where
60
79
pretty Starting = " Starting server"
61
80
pretty (ParsedMsg msg) = " ---> " <> pretty msg
62
81
pretty (SendMsg msg) = " <--2-- " <> pretty msg
82
+ pretty (WebsocketLog msg) = " Websocket:" <+> pretty msg
63
83
64
84
-- ---------------------------------------------------------------------
65
85
@@ -71,18 +91,20 @@ instance Pretty LspServerLog where
71
91
runServer :: forall config . ServerDefinition config -> IO Int
72
92
runServer =
73
93
runServerWithHandles
74
- ioLogger
75
- lspLogger
94
+ defaultIOLogger
95
+ defaultLspLogger
76
96
stdin
77
97
stdout
98
+
99
+ defaultIOLogger :: LogAction IO (WithSeverity LspServerLog )
100
+ defaultIOLogger = L. cmap (show . prettyMsg) L. logStringStderr
78
101
where
79
102
prettyMsg l = " [" <> viaShow (L. getSeverity l) <> " ] " <> pretty (L. getMsg l)
80
- ioLogger :: LogAction IO (WithSeverity LspServerLog )
81
- ioLogger = L. cmap (show . prettyMsg) L. logStringStderr
82
- lspLogger :: LogAction (LspM config ) (WithSeverity LspServerLog )
83
- lspLogger =
84
- let clientLogger = L. cmap (fmap (T. pack . show . pretty)) defaultClientLogger
85
- in clientLogger <> L. hoistLogAction liftIO ioLogger
103
+
104
+ defaultLspLogger :: LogAction (LspM config ) (WithSeverity LspServerLog )
105
+ defaultLspLogger =
106
+ let clientLogger = L. cmap (fmap (T. pack . show . pretty)) defaultClientLogger
107
+ in clientLogger <> L. hoistLogAction liftIO defaultIOLogger
86
108
87
109
{- | Starts a language server over the specified handles.
88
110
This function will return once the @exit@ notification is received.
@@ -116,27 +138,54 @@ runServerWithHandles ioLogger logger hin hout serverDefinition = do
116
138
117
139
{- | Starts listening and sending requests and responses
118
140
using the specified I/O.
141
+
142
+ Assumes that the client sends (and wants to receive) the Content-Length
143
+ header. If you do not want this to be the case, use 'runServerWithConfig'
119
144
-}
120
145
runServerWith ::
121
146
-- | The logger to use outside the main body of the server where we can't assume the ability to send messages.
122
147
LogAction IO (WithSeverity LspServerLog ) ->
123
148
-- | The logger to use once the server has started and can successfully send messages.
124
149
LogAction (LspM config ) (WithSeverity LspServerLog ) ->
125
150
-- | Client input.
126
- IO BS. ByteString ->
151
+ IO BS. StrictByteString ->
127
152
-- | Function to provide output to.
128
- (BSL. ByteString -> IO () ) ->
153
+ (BSL. LazyByteString -> IO () ) ->
129
154
ServerDefinition config ->
130
155
IO Int -- exit code
131
- runServerWith ioLogger logger clientIn clientOut serverDefinition = do
156
+ runServerWith ioLogger lspLogger inwards outwards =
157
+ runServerWithConfig ServerConfig {prepareOutwards = prependHeader, parseInwards = parseHeaders, .. }
158
+
159
+ -- ---------------------------------------------------------------------
160
+
161
+ data ServerConfig config = ServerConfig
162
+ { ioLogger :: LogAction IO (WithSeverity LspServerLog )
163
+ -- ^ The logger to use outside the main body of the server where we can't assume the ability to send messages.
164
+ , lspLogger :: LogAction (LspM config ) (WithSeverity LspServerLog )
165
+ -- ^ The logger to use once the server has started and can successfully send messages.
166
+ , inwards :: IO BS. StrictByteString
167
+ -- ^ Client input.
168
+ , outwards :: BSL. LazyByteString -> IO ()
169
+ -- ^ Function to provide output to.
170
+ , prepareOutwards :: BSL. LazyByteString -> BSL. LazyByteString
171
+ -- ^ how to prepare an outgoing response for sending. This can be used, to e.g. prepend the Content-Length header, c.f. 'prependHeader'
172
+ , parseInwards :: Attoparsec. Parser BS. StrictByteString
173
+ -- ^ how to parse the input. This can be used to consume the Content-Length and Content-Type headers, c.f. 'parseHeaders'
174
+ }
175
+
176
+ runServerWithConfig ::
177
+ ServerConfig config ->
178
+ ServerDefinition config ->
179
+ IO Int
180
+ runServerWithConfig ServerConfig {.. } serverDefinition = do
132
181
ioLogger <& Starting `WithSeverity ` Info
133
182
134
183
cout <- atomically newTChan :: IO (TChan J. Value )
135
- _rhpid <- forkIO $ sendServer ioLogger cout clientOut
184
+ _rhpid <- forkIO $ sendServer ioLogger cout outwards prepareOutwards
136
185
137
186
let sendMsg msg = atomically $ writeTChan cout $ J. toJSON msg
138
187
139
- ioLoop ioLogger logger clientIn serverDefinition emptyVFS sendMsg
188
+ ioLoop ioLogger lspLogger inwards parseInwards serverDefinition emptyVFS sendMsg
140
189
141
190
return 1
142
191
@@ -146,12 +195,13 @@ ioLoop ::
146
195
forall config .
147
196
LogAction IO (WithSeverity LspServerLog ) ->
148
197
LogAction (LspM config ) (WithSeverity LspServerLog ) ->
149
- IO BS. ByteString ->
198
+ IO BS. StrictByteString ->
199
+ Attoparsec. Parser BS. StrictByteString ->
150
200
ServerDefinition config ->
151
201
VFS ->
152
202
(FromServerMessage -> IO () ) ->
153
203
IO ()
154
- ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg = do
204
+ ioLoop ioLogger logger clientIn parser serverDefinition vfs sendMsg = do
155
205
minitialize <- parseOne ioLogger clientIn (parse parser " " )
156
206
case minitialize of
157
207
Nothing -> pure ()
@@ -167,7 +217,7 @@ ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg = do
167
217
pioLogger = L. cmap (fmap LspProcessingLog ) ioLogger
168
218
pLogger = L. cmap (fmap LspProcessingLog ) logger
169
219
170
- loop :: Result BS. ByteString -> LspM config ()
220
+ loop :: Result BS. StrictByteString -> LspM config ()
171
221
loop = go
172
222
where
173
223
go r = do
@@ -178,30 +228,11 @@ ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg = do
178
228
Processing. processMessage pLogger $ BSL. fromStrict msg
179
229
go (parse parser remainder)
180
230
181
- parser = do
182
- try contentType <|> return ()
183
- len <- contentLength
184
- try contentType <|> return ()
185
- _ <- string _ONE_CRLF
186
- Attoparsec. take len
187
-
188
- contentLength = do
189
- _ <- string " Content-Length: "
190
- len <- decimal
191
- _ <- string _ONE_CRLF
192
- return len
193
-
194
- contentType = do
195
- _ <- string " Content-Type: "
196
- skipWhile (/= ' \r ' )
197
- _ <- string _ONE_CRLF
198
- return ()
199
-
200
231
parseOne ::
201
232
MonadIO m =>
202
233
LogAction m (WithSeverity LspServerLog ) ->
203
- IO BS. ByteString ->
204
- Result BS. ByteString ->
234
+ IO BS. StrictByteString ->
235
+ Result BS. StrictByteString ->
205
236
m (Maybe (BS. ByteString , BS. ByteString ))
206
237
parseOne logger clientIn = go
207
238
where
@@ -223,30 +254,155 @@ parseOne logger clientIn = go
223
254
224
255
-- ---------------------------------------------------------------------
225
256
257
+ data WebsocketLog
258
+ = WebsocketShutDown
259
+ | WebsocketNewConnection
260
+ | WebsocketConnectionClosed
261
+ | WebsocketPing
262
+ | WebsocketStarted
263
+ | WebsocketIncomingRequest
264
+ | WebsocketOutgoingResponse
265
+ deriving stock (Show )
266
+
267
+ instance Pretty WebsocketLog where
268
+ pretty l = case l of
269
+ WebsocketPing -> " Ping"
270
+ WebsocketStarted -> " Started Server, waiting for connections"
271
+ WebsocketShutDown -> " Shut down server"
272
+ WebsocketNewConnection -> " New connection established"
273
+ WebsocketIncomingRequest -> " Received request"
274
+ WebsocketConnectionClosed -> " Closed connection to client"
275
+ WebsocketOutgoingResponse -> " Sent response"
276
+
277
+ -- | 'host' and 'port' of the websocket server to set up
278
+ data WebsocketConfig = WebsocketConfig
279
+ { host :: ! String
280
+ -- ^ the host of the websocket server, e.g. @"localhost"@
281
+ , port :: ! Int
282
+ -- ^ the port of the websocket server, e.g. @8080@
283
+ }
284
+
285
+ -- | Set up a websocket server, then call call the continuation (in our case this corresponds to the language server) after accepting a connection
286
+ withWebsocket ::
287
+ -- | The logger
288
+ LogAction IO (WithSeverity LspServerLog ) ->
289
+ -- | The configuration of the websocket server
290
+ WebsocketConfig ->
291
+ -- | invoke the lsp server, passing communication functions
292
+ (IO BS. StrictByteString -> (BSL. LazyByteString -> IO () ) -> IO r ) ->
293
+ IO ()
294
+ withWebsocket logger conf startLspServer = do
295
+ let wsLogger = L. cmap (fmap WebsocketLog ) logger
296
+
297
+ WS. runServer (host conf) (port conf) $ \ pending -> do
298
+ conn <- WS. acceptRequest pending
299
+ wsLogger <& WebsocketNewConnection `WithSeverity ` Debug
300
+
301
+ outChan <- newChan
302
+ inChan <- newChan
303
+
304
+ let inwards = readChan inChan
305
+ outwards = writeChan outChan
306
+
307
+ WS. withPingThread conn 30 (wsLogger <& WebsocketPing `WithSeverity ` Debug ) $ do
308
+ withAsync (startLspServer inwards outwards) $ \ _lspAsync ->
309
+ race_
310
+ ( forever $ do
311
+ msg <- readChan outChan
312
+ wsLogger <& WebsocketOutgoingResponse `WithSeverity ` Debug
313
+ WS. sendTextData conn msg
314
+ )
315
+ ( forever $ do
316
+ msg <- WS. receiveData conn
317
+ wsLogger <& WebsocketIncomingRequest `WithSeverity ` Debug
318
+ writeChan inChan msg
319
+ )
320
+ `finally` do
321
+ wsLogger <& WebsocketConnectionClosed `WithSeverity ` Debug
322
+
323
+ withWebsocketRunServer ::
324
+ -- | Configuration for the websocket
325
+ WebsocketConfig ->
326
+ -- | How to set up a new 'ServerDefinition' for a specific configuration. z
327
+ -- This is passed as CPS'd 'IO' to allow for setting (- and cleaning) up
328
+ -- a server per websocket connection
329
+ ((ServerDefinition config -> IO Int ) -> IO Int ) ->
330
+ -- | The 'IO' logger
331
+ LogAction IO (WithSeverity LspServerLog ) ->
332
+ -- | The logger that logs in 'LspM' to the client
333
+ LogAction (LspM config ) (WithSeverity LspServerLog ) ->
334
+ IO ()
335
+ withWebsocketRunServer wsConf withLspDefinition ioLogger lspLogger =
336
+ withWebsocket ioLogger wsConf $ \ inwards outwards -> do
337
+ withLspDefinition $ \ lspDefinition ->
338
+ runServerWithConfig
339
+ ServerConfig
340
+ { ioLogger
341
+ , lspLogger
342
+ , inwards
343
+ , outwards
344
+ , -- NOTE: if you run the language server on websockets, you do not
345
+ -- need to prepend headers to requests and responses, because
346
+ -- the chunking is already handled by the websocket, i.e. there's
347
+ -- no situation where the client or the server has to rely on input/
348
+ -- output chunking
349
+ prepareOutwards = id
350
+ , parseInwards = Attoparsec. takeByteString
351
+ }
352
+ lspDefinition
353
+
354
+ -- ---------------------------------------------------------------------
355
+
226
356
-- | Simple server to make sure all output is serialised
227
- sendServer :: LogAction IO (WithSeverity LspServerLog ) -> TChan J. Value -> (BSL. ByteString -> IO () ) -> IO ()
228
- sendServer _logger msgChan clientOut = do
357
+ sendServer :: LogAction IO (WithSeverity LspServerLog ) -> TChan J. Value -> (BSL. LazyByteString -> IO () ) -> ( BSL. LazyByteString -> BSL. LazyByteString ) -> IO ()
358
+ sendServer _logger msgChan clientOut prepareMessage = do
229
359
forever $ do
230
360
msg <- atomically $ readTChan msgChan
231
361
232
362
-- We need to make sure we only send over the content of the message,
233
363
-- and no other tags/wrapper stuff
234
364
let str = J. encode msg
235
-
236
- let out =
237
- BSL. concat
238
- [ TL. encodeUtf8 $ TL. pack $ " Content-Length: " ++ show (BSL. length str)
239
- , BSL. fromStrict _TWO_CRLF
240
- , str
241
- ]
365
+ let out = prepareMessage str
242
366
243
367
clientOut out
244
368
245
369
-- TODO: figure out how to re-enable
246
370
-- This can lead to infinite recursion in logging, see https://github.com/haskell/lsp/issues/447
247
371
-- logger <& SendMsg (TL.decodeUtf8 str) `WithSeverity` Debug
248
372
249
- _ONE_CRLF :: BS. ByteString
373
+ -- | prepend a Content-Length header to the given message
374
+ prependHeader :: BSL. LazyByteString -> BSL. LazyByteString
375
+ prependHeader str =
376
+ BSL. concat
377
+ [ TL. encodeUtf8 $ TL. pack $ " Content-Length: " ++ show (BSL. length str)
378
+ , BSL. fromStrict _TWO_CRLF
379
+ , str
380
+ ]
381
+
382
+ {- | parse Content-Length and Content-Type headers and then consume
383
+ input with length of the Content-Length
384
+ -}
385
+ parseHeaders :: Attoparsec. Parser BS. StrictByteString
386
+ parseHeaders = do
387
+ try contentType <|> return ()
388
+ len <- contentLength
389
+ try contentType <|> return ()
390
+ _ <- string _ONE_CRLF
391
+ Attoparsec. take len
392
+ where
393
+ contentLength = do
394
+ _ <- string " Content-Length: "
395
+ len <- decimal
396
+ _ <- string _ONE_CRLF
397
+ return len
398
+
399
+ contentType = do
400
+ _ <- string " Content-Type: "
401
+ skipWhile (/= ' \r ' )
402
+ _ <- string _ONE_CRLF
403
+ return ()
404
+
405
+ _ONE_CRLF :: BS. StrictByteString
250
406
_ONE_CRLF = " \r\n "
251
- _TWO_CRLF :: BS. ByteString
407
+ _TWO_CRLF :: BS. StrictByteString
252
408
_TWO_CRLF = " \r\n\r\n "
0 commit comments