99{-# LANGUAGE RankNTypes #-}
1010{-# LANGUAGE TypeInType #-}
1111{-# LANGUAGE TypeOperators #-}
12+ {-# LANGUAGE TypeApplications #-}
1213
1314module Language.LSP.Test.Session
1415 ( Session (.. )
@@ -41,10 +42,10 @@ import Control.Concurrent hiding (yield)
4142import Control.Exception
4243import Control.Lens hiding (List , Empty )
4344import Control.Monad
44- import Control.Monad.Catch (MonadThrow )
45- import Control.Monad.Except
4645import Control.Monad.IO.Class
4746import Control.Monad.Trans.Class
47+ import Control.Monad.Catch (MonadThrow )
48+ import Control.Monad.Except
4849#if __GLASGOW_HASKELL__ == 806
4950import Control.Monad.Fail
5051#endif
@@ -55,6 +56,7 @@ import qualified Control.Monad.Trans.State as State
5556import qualified Data.ByteString.Lazy.Char8 as B
5657import Data.Aeson hiding (Error , Null )
5758import Data.Aeson.Encode.Pretty
59+ import Data.Aeson.Lens ()
5860import Data.Conduit as Conduit
5961import Data.Conduit.Parser as Parser
6062import Data.Default
@@ -84,6 +86,8 @@ import System.Timeout ( timeout )
8486import Data.IORef
8587import Colog.Core (LogAction (.. ), WithSeverity (.. ), Severity (.. ))
8688import Data.Row
89+ import Data.String (fromString )
90+ import Data.Either (partitionEithers )
8791
8892-- | A session representing one instance of launching and connecting to a server.
8993--
@@ -112,20 +116,26 @@ data SessionConfig = SessionConfig
112116 -- ^ Trace the messages sent and received to stdout, defaults to False.
113117 -- Can be overriden with the environment variable @LSP_TEST_LOG_MESSAGES@.
114118 , 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.
116125 , 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.
121131 , initialWorkspaceFolders :: Maybe [WorkspaceFolder ]
122132 -- ^ The initial workspace folders to send in the @initialize@ request.
123133 -- Defaults to Nothing.
124134 }
125135
126136-- | The configuration used in 'Language.LSP.Test.runSession'.
127137defaultConfig :: SessionConfig
128- defaultConfig = SessionConfig 60 False False True Nothing False Nothing
138+ defaultConfig = SessionConfig 60 False False True mempty True True Nothing
129139
130140instance Default SessionConfig where
131141 def = defaultConfig
@@ -181,7 +191,10 @@ data SessionState = SessionState
181191 , curDynCaps :: ! (Map. Map T. Text SomeRegistration )
182192 -- ^ The capabilities that the server has dynamically registered with us so
183193 -- far
194+ , curLspConfig :: Object
184195 , curProgressSessions :: ! (Set. Set ProgressToken )
196+ , ignoringLogNotifications :: Bool
197+ , ignoringConfigurationRequests :: Bool
185198 }
186199
187200class Monad m => HasState s m where
@@ -227,15 +240,9 @@ runSessionMonad context state (Session session) = runReaderT (runStateT conduit
227240
228241 chanSource = do
229242 msg <- liftIO $ readChan (messageChan context)
230- unless (ignoreLogNotifications (config context) && isLogNotification msg) $
231- yield msg
243+ yield msg
232244 chanSource
233245
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-
239246 watchdog :: ConduitM SessionMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO )) ()
240247 watchdog = Conduit. awaitForever $ \ msg -> do
241248 curId <- getCurTimeoutId
@@ -273,7 +280,7 @@ runSession' serverIn serverOut mServerProc serverHandler config caps rootDir exi
273280 mainThreadId <- myThreadId
274281
275282 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)
277284 runSession' ses = initVFS $ \ vfs -> runSessionMonad context (initState vfs) ses
278285
279286 errorHandler = throwTo mainThreadId :: SessionException -> IO ()
@@ -302,17 +309,42 @@ runSession' serverIn serverOut mServerProc serverHandler config caps rootDir exi
302309
303310updateStateC :: ConduitM FromServerMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO )) ()
304311updateStateC = awaitForever $ \ msg -> do
312+ state <- get @ SessionState
305313 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 ->
311316 sendMessage $ TResponseMessage " 2.0" (Just $ req ^. L. id ) (Right Null )
312- respond ( FromServerMess SMethod_WorkspaceApplyEdit r) = do
317+ FromServerMess SMethod_WorkspaceApplyEdit r -> do
313318 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
315345
346+ isConfigRequest (FromServerMess SMethod_WorkspaceConfiguration _) = True
347+ isConfigRequest _ = False
316348
317349-- extract Uri out from DocumentChange
318350-- didn't put this in `lsp-types` because TH was getting in the way
0 commit comments