11{-# LANGUAGE OverloadedStrings #-}
22{-# LANGUAGE ScopedTypeVariables #-}
33{-# LANGUAGE RankNTypes #-}
4- {-# LANGUAGE LambdaCase #-}
54
65-- So we can keep using the old prettyprinter modules (which have a better
76-- compatibility range) for now.
@@ -17,57 +16,38 @@ module Language.LSP.Server.Control
1716 ) where
1817
1918import qualified Colog.Core as L
20- import Colog.Core (LogAction (.. ), WithSeverity (.. ), Severity (.. ), (<&) )
21- import Control.Concurrent
19+ import Colog.Core (LogAction (.. ), WithSeverity (.. ), Severity (.. ), (<&) , cmap )
20+ import qualified Control.Concurrent.Async as Async
2221import Control.Concurrent.STM.TChan
2322import Control.Applicative ((<|>) )
2423import Control.Monad
2524import Control.Monad.STM
2625import Control.Monad.IO.Class
2726import qualified Data.Aeson as J
28- import qualified Data.Attoparsec.ByteString as Attoparsec
29- import Data.Attoparsec.ByteString.Char8
3027import qualified Data.ByteString as BS
3128import Data.ByteString.Builder.Extra (defaultChunkSize )
32- import qualified Data.ByteString.Lazy as BSL
33- import qualified Data.Text.Lazy as TL
34- import qualified Data.Text.Lazy.Encoding as TL
3529import qualified Data.Text as T
3630import Data.Text.Prettyprint.Doc
37- import Data.List
3831import Language.LSP.Server.Core
3932import qualified Language.LSP.Server.Processing as Processing
4033import Language.LSP.Protocol.Message
4134import Language.LSP.VFS
35+ import qualified Language.LSP.Server.IO as IO
4236import Language.LSP.Logging (defaultClientLogger )
4337import System.IO
4438
4539data LspServerLog =
4640 LspProcessingLog Processing. LspProcessingLog
47- | DecodeInitializeError String
48- | HeaderParseFail [String ] String
49- | EOF
41+ | LspIoLog IO. LspIoLog
5042 | Starting
51- | ParsedMsg T. Text
52- | SendMsg TL. Text
43+ | Stopping
5344 deriving (Show )
5445
5546instance Pretty LspServerLog where
5647 pretty (LspProcessingLog l) = pretty l
57- pretty (DecodeInitializeError err) =
58- vsep [
59- " Got error while decoding initialize:"
60- , pretty err
61- ]
62- pretty (HeaderParseFail ctxs err) =
63- vsep [
64- " Failed to parse message header:"
65- , pretty (intercalate " > " ctxs) <> " : " <+> pretty err
66- ]
67- pretty EOF = " Got EOF"
48+ pretty (LspIoLog l) = pretty l
6849 pretty Starting = " Starting server"
69- pretty (ParsedMsg msg) = " ---> " <> pretty msg
70- pretty (SendMsg msg) = " <--2-- " <> pretty msg
50+ pretty Stopping = " Stopping server"
7151
7252-- ---------------------------------------------------------------------
7353
@@ -116,7 +96,7 @@ runServerWithHandles ioLogger logger hin hout serverDefinition = do
11696 clientIn = BS. hGetSome hin defaultChunkSize
11797
11898 clientOut out = do
119- BSL . hPut hout out
99+ BS . hPut hout out
120100 hFlush hout
121101
122102 runServerWith ioLogger logger clientIn clientOut serverDefinition
@@ -130,134 +110,34 @@ runServerWith ::
130110 -- ^ The logger to use once the server has started and can successfully send messages.
131111 -> IO BS. ByteString
132112 -- ^ Client input.
133- -> (BSL . ByteString -> IO () )
113+ -> (BS . ByteString -> IO () )
134114 -- ^ Function to provide output to.
135115 -> ServerDefinition config
136116 -> IO Int -- exit code
137117runServerWith ioLogger logger clientIn clientOut serverDefinition = do
138118
139119 ioLogger <& Starting `WithSeverity ` Info
140120
141- cout <- atomically newTChan :: IO ( TChan J. Value )
142- _rhpid <- forkIO $ sendServer ioLogger cout clientOut
121+ cout <- atomically newTChan
122+ cin <- atomically newTChan
143123
144- let sendMsg msg = atomically $ writeTChan cout $ J. toJSON msg
124+ let serverOut = IO. serverOut (cmap (fmap LspIoLog ) ioLogger) (atomically $ readTChan cout) clientOut
125+ serverIn = IO. serverIn (cmap (fmap LspIoLog ) ioLogger) (atomically . writeTChan cin) clientIn
145126
146- initVFS $ \ vfs -> do
147- ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg
127+ sendMsg msg = atomically $ writeTChan cout $ J. toJSON msg
128+ recvMsg = atomically $ readTChan cin
148129
149- return 1
150-
151- -- ---------------------------------------------------------------------
152-
153- ioLoop ::
154- forall config
155- . LogAction IO (WithSeverity LspServerLog )
156- -> LogAction (LspM config ) (WithSeverity LspServerLog )
157- -> IO BS. ByteString
158- -> ServerDefinition config
159- -> VFS
160- -> (FromServerMessage -> IO () )
161- -> IO ()
162- ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg = do
163- minitialize <- parseOne ioLogger clientIn (parse parser " " )
164- case minitialize of
165- Nothing -> pure ()
166- Just (msg,remainder) -> do
167- case J. eitherDecode $ BSL. fromStrict msg of
168- Left err -> ioLogger <& DecodeInitializeError err `WithSeverity ` Error
169- Right initialize -> do
170- mInitResp <- Processing. initializeRequestHandler pioLogger serverDefinition vfs sendMsg initialize
171- case mInitResp of
172- Nothing -> pure ()
173- Just env -> runLspT env $ loop (parse parser remainder)
174- where
175-
176- pioLogger = L. cmap (fmap LspProcessingLog ) ioLogger
177- pLogger = L. cmap (fmap LspProcessingLog ) logger
178-
179- loop :: Result BS. ByteString -> LspM config ()
180- loop = go
181- where
182- go r = do
183- res <- parseOne logger clientIn r
184- case res of
185- Nothing -> pure ()
186- Just (msg,remainder) -> do
187- Processing. processMessage pLogger $ BSL. fromStrict msg
188- go (parse parser remainder)
189-
190- parser = do
191- try contentType <|> (return () )
192- len <- contentLength
193- try contentType <|> (return () )
194- _ <- string _ONE_CRLF
195- Attoparsec. take len
196-
197- contentLength = do
198- _ <- string " Content-Length: "
199- len <- decimal
200- _ <- string _ONE_CRLF
201- return len
202-
203- contentType = do
204- _ <- string " Content-Type: "
205- skipWhile (/= ' \r ' )
206- _ <- string _ONE_CRLF
207- return ()
208-
209- parseOne ::
210- MonadIO m
211- => LogAction m (WithSeverity LspServerLog )
212- -> IO BS. ByteString
213- -> Result BS. ByteString
214- -> m (Maybe (BS. ByteString ,BS. ByteString ))
215- parseOne logger clientIn = go
216- where
217- go (Fail _ ctxs err) = do
218- logger <& HeaderParseFail ctxs err `WithSeverity ` Error
219- pure Nothing
220- go (Partial c) = do
221- bs <- liftIO clientIn
222- if BS. null bs
223- then do
224- logger <& EOF `WithSeverity ` Error
225- pure Nothing
226- else go (c bs)
227- go (Done remainder msg) = do
228- -- TODO: figure out how to re-enable
229- -- This can lead to infinite recursion in logging, see https://github.com/haskell/lsp/issues/447
230- -- logger <& ParsedMsg (T.decodeUtf8 msg) `WithSeverity` Debug
231- pure $ Just (msg,remainder)
232-
233- -- ---------------------------------------------------------------------
234-
235- -- | Simple server to make sure all output is serialised
236- sendServer :: LogAction IO (WithSeverity LspServerLog ) -> TChan J. Value -> (BSL. ByteString -> IO () ) -> IO ()
237- sendServer _logger msgChan clientOut = do
238- forever $ do
239- msg <- atomically $ readTChan msgChan
240-
241- -- We need to make sure we only send over the content of the message,
242- -- and no other tags/wrapper stuff
243- let str = J. encode msg
244-
245- let out = BSL. concat
246- [ TL. encodeUtf8 $ TL. pack $ " Content-Length: " ++ show (BSL. length str)
247- , BSL. fromStrict _TWO_CRLF
248- , str ]
249-
250- clientOut out
251- -- TODO: figure out how to re-enable
252- -- This can lead to infinite recursion in logging, see https://github.com/haskell/lsp/issues/447
253- -- logger <& SendMsg (TL.decodeUtf8 str) `WithSeverity` Debug
254-
255- -- |
256- --
257- --
258- _ONE_CRLF :: BS. ByteString
259- _ONE_CRLF = " \r\n "
260- _TWO_CRLF :: BS. ByteString
261- _TWO_CRLF = " \r\n\r\n "
130+ processingLoop = initVFS $ \ vfs ->
131+ Processing. processingLoop
132+ (cmap (fmap LspProcessingLog ) ioLogger)
133+ (cmap (fmap LspProcessingLog ) logger)
134+ vfs
135+ serverDefinition
136+ sendMsg
137+ recvMsg
262138
139+ -- Bind all the threads together so that any of them terminating will terminate everything
140+ serverOut `Async.race_` serverIn `Async.race_` processingLoop
263141
142+ ioLogger <& Stopping `WithSeverity ` Info
143+ return 0
0 commit comments