module Language.Haskell.LSP.Test.Session
( Session
, SessionConfig(..)
+ , defaultConfig
, SessionMessage(..)
, SessionContext(..)
, SessionState(..)
, get
, put
, modify
+ , modifyM
, ask
, asks
, sendMessage
, updateState
, withTimeout
+ , logMsg
+ , LogMsgType(..)
)
where
import qualified Control.Monad.Trans.State as State (get, put)
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Aeson
+import Data.Aeson.Encode.Pretty
import Data.Conduit as Conduit
import Data.Conduit.Parser as Parser
import Data.Default
-- | Stuff you can configure for a 'Session'.
data SessionConfig = SessionConfig
- {
- capabilities :: ClientCapabilities -- ^ Specific capabilities the client should advertise. Default is yes to everything.
- , messageTimeout :: Int -- ^ Maximum time to wait for a message in seconds. Defaults to 60.
- , logStdErr :: Bool -- ^ When True redirects the servers stderr output to haskell-lsp-test's stdout. Defaults to False
+ { messageTimeout :: Int -- ^ Maximum time to wait for a message in seconds, defaults to 60.
+ , logStdErr :: Bool -- ^ Redirect the server's stderr to this stdout, defaults to False.
+ , logMessages :: Bool -- ^ Trace the messages sent and received to stdout, defaults to True.
+ , logColor :: Bool -- ^ Add ANSI color to the logged messages, defaults to True.
}
+defaultConfig :: SessionConfig
+defaultConfig = SessionConfig 60 False True True
+
instance Default SessionConfig where
- def = SessionConfig def 60 False
+ def = defaultConfig
data SessionMessage = ServerMessage FromServerMessage
| TimeoutMessage Int
, requestMap :: MVar RequestMap
, initRsp :: MVar InitializeResponse
, config :: SessionConfig
+ , sessionCapabilities :: ClientCapabilities
}
class Monad m => HasReader r m where
modify :: (s -> s) -> m ()
modify f = get >>= put . f
+ modifyM :: (HasState s m, Monad m) => (s -> m s) -> m ()
+ modifyM f = get >>= f >>= put
+
instance Monad m => HasState s (ParserStateReader a s r m) where
get = lift State.get
put = lift . State.put
yield msg
chanSource
-
watchdog :: ConduitM SessionMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO)) ()
watchdog = Conduit.awaitForever $ \msg -> do
curId <- curTimeoutId <$> get
-> Handle -- ^ Server out
-> (Handle -> SessionContext -> IO ()) -- ^ Server listener
-> SessionConfig
- -> FilePath
+ -> ClientCapabilities
+ -> FilePath -- ^ Root directory
-> Session a
-> IO a
-runSessionWithHandles serverIn serverOut serverHandler config rootDir session = do
+runSessionWithHandles serverIn serverOut serverHandler config caps rootDir session = do
absRootDir <- canonicalizePath rootDir
hSetBuffering serverIn NoBuffering
messageChan <- newChan
initRsp <- newEmptyMVar
- let context = SessionContext serverIn absRootDir messageChan reqMap initRsp config
+ let context = SessionContext serverIn absRootDir messageChan reqMap initRsp config caps
initState = SessionState (IdInt 0) mempty mempty 0 False Nothing
threadId <- forkIO $ void $ serverHandler serverOut context
updateState (ReqApplyWorkspaceEdit r) = do
- oldVFS <- vfs <$> get
-
allChangeParams <- case r ^. params . edit . documentChanges of
Just (List cs) -> do
mapM_ (checkIfNeedsOpened . (^. textDocument . uri)) cs
return $ concatMap (uncurry getChangeParams) (HashMap.toList cs)
Nothing -> error "No changes!"
- newVFS <- liftIO $ changeFromServerVFS oldVFS r
- modify (\s -> s { vfs = newVFS })
+ modifyM $ \s -> do
+ newVFS <- liftIO $ changeFromServerVFS (vfs s) r
+ return $ s { vfs = newVFS }
let groupedParams = groupBy (\a b -> (a ^. textDocument == b ^. textDocument)) allChangeParams
mergedParams = map mergeParams groupedParams
msg = NotificationMessage "2.0" TextDocumentDidOpen (DidOpenTextDocumentParams item)
liftIO $ B.hPut (serverIn ctx) $ addHeader (encode msg)
- oldVFS <- vfs <$> get
- newVFS <- liftIO $ openVFS oldVFS msg
- modify (\s -> s { vfs = newVFS })
+ modifyM $ \s -> do
+ newVFS <- liftIO $ openVFS (vfs s) msg
+ return $ s { vfs = newVFS }
getParams (TextDocumentEdit docId (List edits)) =
let changeEvents = map (\e -> TextDocumentContentChangeEvent (Just (e ^. range)) Nothing (e ^. newText)) edits
sendMessage :: (MonadIO m, HasReader SessionContext m, ToJSON a) => a -> m ()
sendMessage msg = do
h <- serverIn <$> ask
- let encoded = encode msg
- liftIO $ do
-
- setSGR [SetColor Foreground Vivid Cyan]
- putStrLn $ "--> " ++ B.unpack encoded
- setSGR [Reset]
-
- B.hPut h (addHeader encoded)
+ logMsg LogClient msg
+ liftIO $ B.hPut h (addHeader $ encode msg)
-- | Execute a block f that will throw a 'TimeoutException'
-- after duration seconds. This will override the global timeout
overridingTimeout = False
}
return res
+
+-- logClientMsg :: (MonadIO m, HasReader SessionContext m)
+-- => FromClientMessage -> m ()
+-- logClientMsg = logMsg True
+
+-- logServerMsg :: (MonadIO m, HasReader SessionContext m)
+-- => FromServerMessage -> m ()
+-- logServerMsg = logMsg False
+
+data LogMsgType = LogServer | LogClient
+ deriving Eq
+
+-- | Logs the message if the config specified it
+logMsg :: (ToJSON a, MonadIO m, HasReader SessionContext m)
+ => LogMsgType -> a -> m ()
+logMsg t msg = do
+ shouldLog <- asks $ logMessages . config
+ shouldColor <- asks $ logColor . config
+ liftIO $ when shouldLog $ do
+ when shouldColor $ setSGR [SetColor Foreground Dull color]
+ putStrLn $ arrow ++ showPretty msg
+ when shouldColor $ setSGR [Reset]
+
+ where arrow
+ | t == LogServer = "<-- "
+ | otherwise = "--> "
+ color
+ | t == LogServer = Magenta
+ | otherwise = Cyan
+
+
+showPretty :: ToJSON a => a -> String
+showPretty = B.unpack . encodePretty