-
Notifications
You must be signed in to change notification settings - Fork 92
/
Copy pathControl.hs
242 lines (210 loc) · 7.74 KB
/
Control.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE LambdaCase #-}
-- So we can keep using the old prettyprinter modules (which have a better
-- compatibility range) for now.
{-# OPTIONS_GHC -Wno-deprecations #-}
module Language.LSP.Server.Control
(
-- * Running
runServer
, runServerWith
, runServerWithHandles
, LspServerLog (..)
) where
import qualified Colog.Core as L
import Colog.Core (LogAction (..), WithSeverity (..), Severity (..), (<&))
import Control.Concurrent
import Control.Concurrent.STM.TChan
import Control.Monad
import Control.Monad.STM
import Control.Monad.IO.Class
import qualified Data.Aeson as J
import qualified Data.Attoparsec.ByteString as Attoparsec
import Data.Attoparsec.ByteString.Char8
import qualified Data.ByteString as BS
import Data.ByteString.Builder.Extra (defaultChunkSize)
import qualified Data.ByteString.Lazy as BSL
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Encoding as TL
import qualified Data.Text as T
import qualified Data.Text.Encoding as T
import Data.Text.Prettyprint.Doc
import Data.List
import Language.LSP.Server.Core
import qualified Language.LSP.Server.Processing as Processing
import Language.LSP.Types
import Language.LSP.VFS
import Language.LSP.Logging (defaultClientLogger)
import System.IO
data LspServerLog =
LspProcessingLog Processing.LspProcessingLog
| DecodeInitializeError String
| HeaderParseFail [String] String
| EOF
| Starting
| ParsedMsg T.Text
| SendMsg TL.Text
deriving (Show)
instance Pretty LspServerLog where
pretty (LspProcessingLog l) = pretty l
pretty (DecodeInitializeError err) =
vsep [
"Got error while decoding initialize:"
, pretty err
]
pretty (HeaderParseFail ctxs err) =
vsep [
"Failed to parse message header:"
, pretty (intercalate " > " ctxs) <> ": " <+> pretty err
]
pretty EOF = "Got EOF"
pretty Starting = "Starting server"
pretty (ParsedMsg msg) = "---> " <> pretty msg
pretty (SendMsg msg) = "<--2-- " <> pretty msg
-- ---------------------------------------------------------------------
-- | Convenience function for 'runServerWithHandles' which:
-- (1) reads from stdin;
-- (2) writes to stdout; and
-- (3) logs to stderr and to the client, with some basic filtering.
runServer :: forall config . ServerDefinition config -> IO Int
runServer =
runServerWithHandles
ioLogger
lspLogger
stdin
stdout
where
prettyMsg l = "[" <> viaShow (L.getSeverity l) <> "] " <> pretty (L.getMsg l)
ioLogger :: LogAction IO (WithSeverity LspServerLog)
ioLogger = L.cmap (show . prettyMsg) L.logStringStderr
lspLogger :: LogAction (LspM config) (WithSeverity LspServerLog)
lspLogger =
let clientLogger = L.cmap (fmap (T.pack . show . pretty)) defaultClientLogger
in clientLogger <> L.hoistLogAction liftIO ioLogger
-- | Starts a language server over the specified handles.
-- This function will return once the @exit@ notification is received.
runServerWithHandles ::
LogAction IO (WithSeverity LspServerLog)
-- ^ The logger to use outside the main body of the server where we can't assume the ability to send messages.
-> LogAction (LspM config) (WithSeverity LspServerLog)
-- ^ The logger to use once the server has started and can successfully send messages.
-> Handle
-- ^ Handle to read client input from.
-> Handle
-- ^ Handle to write output to.
-> ServerDefinition config
-> IO Int -- exit code
runServerWithHandles ioLogger logger hin hout serverDefinition = do
hSetBuffering hin NoBuffering
hSetEncoding hin utf8
hSetBuffering hout NoBuffering
hSetEncoding hout utf8
let
clientIn = BS.hGetSome hin defaultChunkSize
clientOut out = do
BSL.hPut hout out
hFlush hout
runServerWith ioLogger logger clientIn clientOut serverDefinition
-- | Starts listening and sending requests and responses
-- using the specified I/O.
runServerWith ::
LogAction IO (WithSeverity LspServerLog)
-- ^ The logger to use outside the main body of the server where we can't assume the ability to send messages.
-> LogAction (LspM config) (WithSeverity LspServerLog)
-- ^ The logger to use once the server has started and can successfully send messages.
-> IO BS.ByteString
-- ^ Client input.
-> (BSL.ByteString -> IO ())
-- ^ Function to provide output to.
-> ServerDefinition config
-> IO Int -- exit code
runServerWith ioLogger logger clientIn clientOut serverDefinition = do
ioLogger <& Starting `WithSeverity` Info
cout <- atomically newTChan :: IO (TChan J.Value)
_rhpid <- forkIO $ sendServer ioLogger cout clientOut
let sendMsg msg = atomically $ writeTChan cout $ J.toJSON msg
initVFS $ \vfs -> do
ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg
return 1
-- ---------------------------------------------------------------------
ioLoop ::
forall config
. LogAction IO (WithSeverity LspServerLog)
-> LogAction (LspM config) (WithSeverity LspServerLog)
-> IO BS.ByteString
-> ServerDefinition config
-> VFS
-> (FromServerMessage -> IO ())
-> IO ()
ioLoop ioLogger logger clientIn serverDefinition vfs sendMsg = do
minitialize <- parseOne ioLogger clientIn (parse parser "")
case minitialize of
Nothing -> pure ()
Just (msg,remainder) -> do
case J.eitherDecode $ BSL.fromStrict msg of
Left err -> ioLogger <& DecodeInitializeError err `WithSeverity` Error
Right initialize -> do
mInitResp <- Processing.initializeRequestHandler serverDefinition vfs sendMsg initialize
case mInitResp of
Nothing -> pure ()
Just env -> runLspT env $ loop (parse parser remainder)
where
loop :: Result BS.ByteString -> LspM config ()
loop = go
where
pLogger = L.cmap (fmap LspProcessingLog) logger
go r = do
res <- parseOne logger clientIn r
case res of
Nothing -> pure ()
Just (msg,remainder) -> do
Processing.processMessage pLogger $ BSL.fromStrict msg
go (parse parser remainder)
parser = do
_ <- string "Content-Length: "
len <- decimal
_ <- string _TWO_CRLF
Attoparsec.take len
parseOne ::
MonadIO m
=> LogAction m (WithSeverity LspServerLog)
-> IO BS.ByteString
-> Result BS.ByteString
-> m (Maybe (BS.ByteString,BS.ByteString))
parseOne logger clientIn = go
where
go (Fail _ ctxs err) = do
logger <& HeaderParseFail ctxs err `WithSeverity` Error
pure Nothing
go (Partial c) = do
bs <- liftIO clientIn
if BS.null bs
then do
logger <& EOF `WithSeverity` Error
pure Nothing
else go (c bs)
go (Done remainder msg) = do
logger <& ParsedMsg (T.decodeUtf8 msg) `WithSeverity` Debug
pure $ Just (msg,remainder)
-- ---------------------------------------------------------------------
-- | Simple server to make sure all output is serialised
sendServer :: LogAction IO (WithSeverity LspServerLog) -> TChan J.Value -> (BSL.ByteString -> IO ()) -> IO ()
sendServer logger msgChan clientOut = do
forever $ do
msg <- atomically $ readTChan msgChan
-- We need to make sure we only send over the content of the message,
-- and no other tags/wrapper stuff
let str = J.encode msg
let out = BSL.concat
[ TL.encodeUtf8 $ TL.pack $ "Content-Length: " ++ show (BSL.length str)
, BSL.fromStrict _TWO_CRLF
, str ]
clientOut out
logger <& SendMsg (TL.decodeUtf8 str) `WithSeverity` Debug
-- |
--
--
_TWO_CRLF :: BS.ByteString
_TWO_CRLF = "\r\n\r\n"