9
9
{-# LANGUAGE RankNTypes #-}
10
10
{-# LANGUAGE TypeInType #-}
11
11
{-# LANGUAGE TypeOperators #-}
12
+ {-# LANGUAGE TypeApplications #-}
12
13
13
14
module Language.LSP.Test.Session
14
15
( Session (.. )
@@ -41,10 +42,10 @@ import Control.Concurrent hiding (yield)
41
42
import Control.Exception
42
43
import Control.Lens hiding (List , Empty )
43
44
import Control.Monad
44
- import Control.Monad.Catch (MonadThrow )
45
- import Control.Monad.Except
46
45
import Control.Monad.IO.Class
47
46
import Control.Monad.Trans.Class
47
+ import Control.Monad.Catch (MonadThrow )
48
+ import Control.Monad.Except
48
49
#if __GLASGOW_HASKELL__ == 806
49
50
import Control.Monad.Fail
50
51
#endif
@@ -55,6 +56,7 @@ import qualified Control.Monad.Trans.State as State
55
56
import qualified Data.ByteString.Lazy.Char8 as B
56
57
import Data.Aeson hiding (Error , Null )
57
58
import Data.Aeson.Encode.Pretty
59
+ import Data.Aeson.Lens ()
58
60
import Data.Conduit as Conduit
59
61
import Data.Conduit.Parser as Parser
60
62
import Data.Default
@@ -84,6 +86,8 @@ import System.Timeout ( timeout )
84
86
import Data.IORef
85
87
import Colog.Core (LogAction (.. ), WithSeverity (.. ), Severity (.. ))
86
88
import Data.Row
89
+ import Data.String (fromString )
90
+ import Data.Either (partitionEithers )
87
91
88
92
-- | A session representing one instance of launching and connecting to a server.
89
93
--
@@ -112,20 +116,26 @@ data SessionConfig = SessionConfig
112
116
-- ^ Trace the messages sent and received to stdout, defaults to False.
113
117
-- Can be overriden with the environment variable @LSP_TEST_LOG_MESSAGES@.
114
118
, logColor :: Bool -- ^ Add ANSI color to the logged messages, defaults to True.
115
- , lspConfig :: Maybe Value -- ^ The initial LSP config as JSON value, defaults to Nothing.
119
+ , lspConfig :: Object
120
+ -- ^ The initial LSP config as JSON object, defaults to the empty object.
121
+ -- This should include the config section for the server if it has one, i.e. if
122
+ -- the server has a 'mylang' config section, then the config should be an object
123
+ -- with a 'mylang' key whose value is the actual config for the server. You
124
+ -- can also include other config sections if your server may request those.
116
125
, ignoreLogNotifications :: Bool
117
- -- ^ Whether or not to ignore 'Language.LSP.Types.ShowMessageNotification' and
118
- -- 'Language.LSP.Types.LogMessageNotification', defaults to False.
119
- --
120
- -- @since 0.9.0.0
126
+ -- ^ Whether or not to ignore @window/showMessage@ and @window/logMessage@ notifications
127
+ -- from the server, defaults to True.
128
+ , ignoreConfigurationRequests :: Bool
129
+ -- ^ Whether or not to ignore @workspace/configuration@ requests from the server,
130
+ -- defaults to True.
121
131
, initialWorkspaceFolders :: Maybe [WorkspaceFolder ]
122
132
-- ^ The initial workspace folders to send in the @initialize@ request.
123
133
-- Defaults to Nothing.
124
134
}
125
135
126
136
-- | The configuration used in 'Language.LSP.Test.runSession'.
127
137
defaultConfig :: SessionConfig
128
- defaultConfig = SessionConfig 60 False False True Nothing False Nothing
138
+ defaultConfig = SessionConfig 60 False False True mempty True True Nothing
129
139
130
140
instance Default SessionConfig where
131
141
def = defaultConfig
@@ -181,7 +191,10 @@ data SessionState = SessionState
181
191
, curDynCaps :: ! (Map. Map T. Text SomeRegistration )
182
192
-- ^ The capabilities that the server has dynamically registered with us so
183
193
-- far
194
+ , curLspConfig :: Object
184
195
, curProgressSessions :: ! (Set. Set ProgressToken )
196
+ , ignoringLogNotifications :: Bool
197
+ , ignoringConfigurationRequests :: Bool
185
198
}
186
199
187
200
class Monad m => HasState s m where
@@ -227,15 +240,9 @@ runSessionMonad context state (Session session) = runReaderT (runStateT conduit
227
240
228
241
chanSource = do
229
242
msg <- liftIO $ readChan (messageChan context)
230
- unless (ignoreLogNotifications (config context) && isLogNotification msg) $
231
- yield msg
243
+ yield msg
232
244
chanSource
233
245
234
- isLogNotification (ServerMessage (FromServerMess SMethod_WindowShowMessage _)) = True
235
- isLogNotification (ServerMessage (FromServerMess SMethod_WindowLogMessage _)) = True
236
- isLogNotification (ServerMessage (FromServerMess SMethod_WindowShowDocument _)) = True
237
- isLogNotification _ = False
238
-
239
246
watchdog :: ConduitM SessionMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO )) ()
240
247
watchdog = Conduit. awaitForever $ \ msg -> do
241
248
curId <- getCurTimeoutId
@@ -273,7 +280,7 @@ runSession' serverIn serverOut mServerProc serverHandler config caps rootDir exi
273
280
mainThreadId <- myThreadId
274
281
275
282
let context = SessionContext serverIn absRootDir messageChan timeoutIdVar reqMap initRsp config caps
276
- initState vfs = SessionState 0 vfs mempty False Nothing mempty mempty
283
+ initState vfs = SessionState 0 vfs mempty False Nothing mempty (lspConfig config) mempty (ignoreLogNotifications config) (ignoreConfigurationRequests config)
277
284
runSession' ses = initVFS $ \ vfs -> runSessionMonad context (initState vfs) ses
278
285
279
286
errorHandler = throwTo mainThreadId :: SessionException -> IO ()
@@ -302,17 +309,42 @@ runSession' serverIn serverOut mServerProc serverHandler config caps rootDir exi
302
309
303
310
updateStateC :: ConduitM FromServerMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO )) ()
304
311
updateStateC = awaitForever $ \ msg -> do
312
+ state <- get @ SessionState
305
313
updateState msg
306
- respond msg
307
- yield msg
308
- where
309
- respond :: (MonadIO m , HasReader SessionContext m ) => FromServerMessage -> m ()
310
- respond (FromServerMess SMethod_WindowWorkDoneProgressCreate req) =
314
+ case msg of
315
+ FromServerMess SMethod_WindowWorkDoneProgressCreate req ->
311
316
sendMessage $ TResponseMessage " 2.0" (Just $ req ^. L. id ) (Right Null )
312
- respond ( FromServerMess SMethod_WorkspaceApplyEdit r) = do
317
+ FromServerMess SMethod_WorkspaceApplyEdit r -> do
313
318
sendMessage $ TResponseMessage " 2.0" (Just $ r ^. L. id ) (Right $ ApplyWorkspaceEditResult True Nothing Nothing )
314
- respond _ = pure ()
319
+ FromServerMess SMethod_WorkspaceConfiguration r -> do
320
+ let requestedSections = mapMaybe (\ i -> i ^? L. section . _Just) $ r ^. L. params . L. items
321
+ let o = curLspConfig state
322
+ -- check for each requested section whether we have it
323
+ let configsOrErrs = (flip fmap ) requestedSections $ \ section ->
324
+ case o ^. at (fromString $ T. unpack section) of
325
+ Just config -> Right config
326
+ Nothing -> Left section
327
+
328
+ let (errs, configs) = partitionEithers configsOrErrs
329
+
330
+ -- we have to return exactly the number of sections requested, so if we can't find all of them then that's an error
331
+ if null errs
332
+ then sendMessage $ TResponseMessage " 2.0" (Just $ r ^. L. id ) (Right configs)
333
+ else sendMessage @ _ @ (TResponseError Method_WorkspaceConfiguration ) $
334
+ TResponseError (InL LSPErrorCodes_RequestFailed ) (" No configuration for requested sections: " <> (T. pack $ show errs)) Nothing
335
+ _ -> pure ()
336
+ unless ((ignoringLogNotifications state && isLogNotification msg) || (ignoringConfigurationRequests state && isConfigRequest msg)) $
337
+ yield msg
338
+
339
+ where
340
+
341
+ isLogNotification (FromServerMess SMethod_WindowShowMessage _) = True
342
+ isLogNotification (FromServerMess SMethod_WindowLogMessage _) = True
343
+ isLogNotification (FromServerMess SMethod_WindowShowDocument _) = True
344
+ isLogNotification _ = False
315
345
346
+ isConfigRequest (FromServerMess SMethod_WorkspaceConfiguration _) = True
347
+ isConfigRequest _ = False
316
348
317
349
-- extract Uri out from DocumentChange
318
350
-- didn't put this in `lsp-types` because TH was getting in the way
0 commit comments