, MonadSessionConfig(..)
, SessionException(..)
, anySessionException
+ , withTimeout
-- * Sending
, sendRequest
, sendRequest_
, sendNotification'
, sendResponse
-- * Receving
+ , message
, anyRequest
- , request
, anyResponse
- , response
, anyNotification
- , notification
, anyMessage
, loggingNotification
, publishDiagnosticsNotification
import qualified Data.HashMap.Strict as HashMap
import qualified Data.Map as Map
import Data.Maybe
-import Language.Haskell.LSP.Types hiding (id, capabilities)
+import Language.Haskell.LSP.Types hiding (id, capabilities, message)
import qualified Language.Haskell.LSP.Types as LSP
import Language.Haskell.LSP.Messages
import Language.Haskell.LSP.VFS
reqMap <- readMVar $ requestMap context
let msg = decodeFromServerMsg reqMap msgBytes
- writeChan (messageChan context) msg
+ writeChan (messageChan context) (ServerMessage msg)
listenServer serverOut context
-- and returns the new content
getDocumentEdit :: TextDocumentIdentifier -> Session T.Text
getDocumentEdit doc = do
- req <- request :: Session ApplyWorkspaceEditRequest
+ req <- message :: Session ApplyWorkspaceEditRequest
unless (checkDocumentChanges req || checkChanges req) $
liftIO $ throw (IncorrectApplyEditRequestException (show req))
waitForDiagnostics :: Session [Diagnostic]
waitForDiagnostics = do
- diagsNot <- skipManyTill anyMessage notification :: Session PublishDiagnosticsNotification
+ diagsNot <- skipManyTill anyMessage message :: Session PublishDiagnosticsNotification
let (List diags) = diagsNot ^. params . LSP.diagnostics
return diags
-- returned.
noDiagnostics :: Session ()
noDiagnostics = do
- diagsNot <- notification :: Session PublishDiagnosticsNotification
+ diagsNot <- message :: Session PublishDiagnosticsNotification
when (diagsNot ^. params . LSP.diagnostics /= List []) $ liftIO $ throw UnexpectedDiagnosticsException
-- | Returns the symbols in a document.
where handleEdit :: WorkspaceEdit -> Session ()
handleEdit e =
let req = RequestMessage "" (IdInt 0) WorkspaceApplyEdit (ApplyWorkspaceEditParams e)
- in processMessage (ReqApplyWorkspaceEdit req)
+ in updateState (ReqApplyWorkspaceEdit req)
{-# OPTIONS_GHC -Wunused-imports #-}
module Language.Haskell.LSP.Test.Compat where
-import Control.Concurrent.Chan
-import Control.Monad.IO.Class
-import Data.Conduit
import Data.Maybe
#if MIN_VERSION_process(1,6,3)
#endif
_ -> return Nothing
#endif
-
-#if MIN_VERSION_conduit(1,3,0)
-chanSource :: MonadIO m => Chan o -> ConduitT i o m b
-#else
-chanSource :: MonadIO m => Chan o -> ConduitM i o m b
-#endif
-chanSource c = do
- x <- liftIO $ readChan c
- yield x
- chanSource c
| UnexpectedDiagnosticsException
| IncorrectApplyEditRequestException String
| UnexpectedResponseError LspIdRsp ResponseError
+ deriving Eq
instance Exception SessionException
show TimeoutException = "Timed out waiting to receive a message from the server."
show (UnexpectedMessageException expected lastMsg) =
"Received an unexpected message from the server:\n" ++
- "Expected: " ++ expected ++ "\n" ++
- "Last message accepted: " ++ show lastMsg
+ "Was parsing: " ++ expected ++ "\n" ++
+ "Last message received: " ++ show lastMsg
show (ReplayOutOfOrderException received expected) =
"Replay is out of order:\n" ++
-- Print json so its a bit easier to update the session logs
import Control.Concurrent
import Control.Lens
import Control.Monad.IO.Class
-import Control.Monad.Trans.Class
+import Control.Monad
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Conduit.Parser
import Data.Maybe
+import qualified Data.Text as T
+import Data.Typeable
import Language.Haskell.LSP.Messages
import Language.Haskell.LSP.Types as LSP hiding (error)
-import Language.Haskell.LSP.Test.Exceptions
import Language.Haskell.LSP.Test.Messages
import Language.Haskell.LSP.Test.Session
import System.Console.ANSI
-satisfy :: (MonadIO m, MonadSessionConfig m) => (FromServerMessage -> Bool) -> ConduitParser FromServerMessage m FromServerMessage
+satisfy :: (FromServerMessage -> Bool) -> Session FromServerMessage
satisfy pred = do
- timeout <- timeout <$> lift sessionConfig
- tId <- liftIO myThreadId
- timeoutThread <- liftIO $ forkIO $ do
+
+ skipTimeout <- overridingTimeout <$> get
+ timeoutId <- curTimeoutId <$> get
+ unless skipTimeout $ do
+ chan <- asks messageChan
+ timeout <- asks (messageTimeout . config)
+ void $ liftIO $ forkIO $ do
threadDelay (timeout * 1000000)
- throwTo tId TimeoutException
+ writeChan chan (TimeoutMessage timeoutId)
+
x <- await
- liftIO $ killThread timeoutThread
+
+ unless skipTimeout $
+ modify $ \s -> s { curTimeoutId = timeoutId + 1 }
+
+ modify $ \s -> s { lastReceivedMessage = Just x }
if pred x
then do
return x
else empty
--- | Matches if the message is a notification.
-anyNotification :: (MonadIO m, MonadSessionConfig m) => ConduitParser FromServerMessage m FromServerMessage
-anyNotification = named "Any notification" $ satisfy isServerNotification
-
-notification :: forall m a. (MonadIO m, MonadSessionConfig m, FromJSON a) => ConduitParser FromServerMessage m (NotificationMessage ServerMethod a)
-notification = named "Notification" $ do
- let parser = decode . encodeMsg :: FromServerMessage -> Maybe (NotificationMessage ServerMethod a)
+-- | Matches a message of type 'a'.
+message :: forall a. (Typeable a, FromJSON a) => Session a
+message =
+ let parser = decode . encodeMsg :: FromServerMessage -> Maybe a
+ in named (T.pack $ show $ head $ snd $ splitTyConApp $ last $ typeRepArgs $ typeOf parser) $ do
x <- satisfy (isJust . parser)
return $ castMsg x
+-- | Matches if the message is a notification.
+anyNotification :: Session FromServerMessage
+anyNotification = named "Any notification" $ satisfy isServerNotification
+
-- | Matches if the message is a request.
-anyRequest :: (MonadIO m, MonadSessionConfig m) => ConduitParser FromServerMessage m FromServerMessage
+anyRequest :: Session FromServerMessage
anyRequest = named "Any request" $ satisfy isServerRequest
-request :: forall m a b. (MonadIO m, MonadSessionConfig m, FromJSON a, FromJSON b) => ConduitParser FromServerMessage m (RequestMessage ServerMethod a b)
-request = named "Request" $ do
- let parser = decode . encodeMsg :: FromServerMessage -> Maybe (RequestMessage ServerMethod a b)
- x <- satisfy (isJust . parser)
- return $ castMsg x
-
-- | Matches if the message is a response.
-anyResponse :: (MonadIO m, MonadSessionConfig m) => ConduitParser FromServerMessage m FromServerMessage
+anyResponse :: Session FromServerMessage
anyResponse = named "Any response" $ satisfy isServerResponse
-response :: forall m a. (MonadIO m, MonadSessionConfig m, FromJSON a) => ConduitParser FromServerMessage m (ResponseMessage a)
-response = named "Response" $ do
- let parser = decode . encodeMsg :: FromServerMessage -> Maybe (ResponseMessage a)
- x <- satisfy (isJust . parser)
- return $ castMsg x
-
-responseForId :: forall m a. (MonadIO m, MonadSessionConfig m, FromJSON a) => LspId -> ConduitParser FromServerMessage m (ResponseMessage a)
-responseForId lid = named "Response for id" $ do
+responseForId :: forall a. FromJSON a => LspId -> Session (ResponseMessage a)
+responseForId lid = named (T.pack $ "Response for id: " ++ show lid) $ do
let parser = decode . encodeMsg :: FromServerMessage -> Maybe (ResponseMessage a)
x <- satisfy (maybe False (\z -> z ^. LSP.id == responseId lid) . parser)
return $ castMsg x
-anyMessage :: (MonadIO m, MonadSessionConfig m) => ConduitParser FromServerMessage m FromServerMessage
+anyMessage :: Session FromServerMessage
anyMessage = satisfy (const True)
-- | A stupid method for getting out the inner message.
encodeMsg = encode . genericToJSON (defaultOptions { sumEncoding = UntaggedValue })
-- | Matches if the message is a log message notification or a show message notification/request.
-loggingNotification :: (MonadIO m, MonadSessionConfig m) => ConduitParser FromServerMessage m FromServerMessage
+loggingNotification :: Session FromServerMessage
loggingNotification = named "Logging notification" $ satisfy shouldSkip
where
shouldSkip (NotLogMessage _) = True
shouldSkip (ReqShowMessage _) = True
shouldSkip _ = False
-publishDiagnosticsNotification :: (MonadIO m, MonadSessionConfig m) => ConduitParser FromServerMessage m PublishDiagnosticsNotification
+publishDiagnosticsNotification :: Session PublishDiagnosticsNotification
publishDiagnosticsNotification = named "Publish diagnostics notification" $ do
NotPublishDiagnostics diags <- satisfy test
return diags
module Language.Haskell.LSP.Test.Session
( Session
, SessionConfig(..)
+ , SessionMessage(..)
, SessionContext(..)
, SessionState(..)
, MonadSessionConfig(..)
, ask
, asks
, sendMessage
- , processMessage)
+ , updateState
+ , withTimeout
+ )
where
import qualified Control.Monad.Trans.State as State (get, put)
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Aeson
-import Data.Conduit hiding (await)
-import Data.Conduit.Parser
+import Data.Conduit as Conduit
+import Data.Conduit.Parser as Parser
import Data.Default
import Data.Foldable
import Data.List
import Language.Haskell.LSP.TH.ClientCapabilities
import Language.Haskell.LSP.Types hiding (error)
import Language.Haskell.LSP.VFS
-import Language.Haskell.LSP.Test.Compat
import Language.Haskell.LSP.Test.Decoding
import Language.Haskell.LSP.Test.Exceptions
import System.Console.ANSI
data SessionConfig = SessionConfig
{
capabilities :: ClientCapabilities -- ^ Specific capabilities the client should advertise. Default is yes to everything.
- , timeout :: Int -- ^ Maximum time to wait for a request in seconds. Defaults to 60.
+ , 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
}
instance Monad m => MonadSessionConfig (StateT SessionState (ReaderT SessionContext m)) where
sessionConfig = config <$> lift Reader.ask
+data SessionMessage = ServerMessage FromServerMessage
+ | TimeoutMessage Int
+ deriving Show
+
data SessionContext = SessionContext
{
serverIn :: Handle
, rootDir :: FilePath
- , messageChan :: Chan FromServerMessage
+ , messageChan :: Chan SessionMessage
, requestMap :: MVar RequestMap
, initRsp :: MVar InitializeResponse
, config :: SessionConfig
instance Monad m => HasReader r (ParserStateReader a s r m) where
ask = lift $ lift Reader.ask
-instance HasReader SessionContext SessionProcessor where
+instance Monad m => HasReader SessionContext (ConduitM a b (StateT s (ReaderT SessionContext m))) where
ask = lift $ lift Reader.ask
data SessionState = SessionState
curReqId :: LspId
, vfs :: VFS
, curDiagnostics :: Map.Map Uri [Diagnostic]
+ , curTimeoutId :: Int
+ , overridingTimeout :: Bool
+ -- ^ The last received message from the server.
+ -- Used for providing exception information
+ , lastReceivedMessage :: Maybe FromServerMessage
}
class Monad m => HasState s m where
get = lift State.get
put = lift . State.put
-instance HasState SessionState SessionProcessor where
+instance Monad m => HasState SessionState (ConduitM a b (StateT SessionState m))
+ where
get = lift State.get
put = lift . State.put
type ParserStateReader a s r m = ConduitParser a (StateT s (ReaderT r m))
-type SessionProcessor = ConduitM FromServerMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO))
-
-runSession :: Chan FromServerMessage -> SessionProcessor () -> SessionContext -> SessionState -> Session a -> IO (a, SessionState)
-runSession chan preprocessor context state session = runReaderT (runStateT conduit state) context
- where conduit = runConduit $ chanSource chan .| preprocessor .| runConduitParser (catchError session handler)
- handler e@(Unexpected "ConduitParser.empty") = do
+runSession :: SessionContext -> SessionState -> Session a -> IO (a, SessionState)
+runSession context state session =
+ -- source <- sourceList <$> getChanContents (messageChan context)
+ runReaderT (runStateT conduit state) context
+ where
+ conduit = runConduit $ chanSource .| watchdog .| updateStateC .| runConduitParser (catchError session handler)
- -- Horrible way to get last item in conduit:
- -- Add a fake message so we can tell when to stop
- liftIO $ writeChan chan (RspShutdown (ResponseMessage "EMPTY" IdRspNull Nothing Nothing))
- x <- peek
- case x of
- Just x -> do
- lastMsg <- skipToEnd x
+ handler (Unexpected "ConduitParser.empty") = do
+ lastMsg <- fromJust . lastReceivedMessage <$> get
name <- getParserName
liftIO $ throw (UnexpectedMessageException (T.unpack name) lastMsg)
- Nothing -> throw e
handler e = throw e
- skipToEnd x = do
- y <- peek
- case y of
- Just (RspShutdown (ResponseMessage "EMPTY" IdRspNull Nothing Nothing)) -> return x
- Just _ -> await >>= skipToEnd
- Nothing -> return x
+ chanSource = do
+ msg <- liftIO $ readChan (messageChan context)
+ yield msg
+ chanSource
+
+
+ watchdog :: ConduitM SessionMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO)) ()
+ watchdog = Conduit.awaitForever $ \msg -> do
+ curId <- curTimeoutId <$> get
+ case msg of
+ ServerMessage sMsg -> yield sMsg
+ TimeoutMessage tId -> when (curId == tId) $ throw TimeoutException
-- | An internal version of 'runSession' that allows for a custom handler to listen to the server.
-- It also does not automatically send initialize and exit messages.
initRsp <- newEmptyMVar
let context = SessionContext serverIn absRootDir messageChan reqMap initRsp config
- initState = SessionState (IdInt 0) mempty mempty
+ initState = SessionState (IdInt 0) mempty mempty 0 False Nothing
threadId <- forkIO $ void $ serverHandler serverOut context
- (result, _) <- runSession messageChan processor context initState session
+ (result, _) <- runSession context initState session
killThread threadId
return result
- where processor :: SessionProcessor ()
- processor = awaitForever $ \msg -> do
- processMessage msg
+updateStateC :: ConduitM FromServerMessage FromServerMessage (StateT SessionState (ReaderT SessionContext IO)) ()
+updateStateC = awaitForever $ \msg -> do
+ updateState msg
yield msg
-
-processMessage :: (MonadIO m, HasReader SessionContext m, HasState SessionState m) => FromServerMessage -> m ()
-processMessage (NotPublishDiagnostics n) = do
+updateState :: (MonadIO m, HasReader SessionContext m, HasState SessionState m) => FromServerMessage -> m ()
+updateState (NotPublishDiagnostics n) = do
let List diags = n ^. params . diagnostics
doc = n ^. params . uri
modify (\s ->
let newDiags = Map.insert doc diags (curDiagnostics s)
in s { curDiagnostics = newDiags })
-processMessage (ReqApplyWorkspaceEdit r) = do
+updateState (ReqApplyWorkspaceEdit r) = do
allChangeParams <- case r ^. params . edit . documentChanges of
Just (List cs) -> do
mergeParams :: [DidChangeTextDocumentParams] -> DidChangeTextDocumentParams
mergeParams params = let events = concat (toList (map (toList . (^. contentChanges)) params))
in DidChangeTextDocumentParams (head params ^. textDocument) (List events)
-processMessage _ = return ()
+updateState _ = return ()
sendMessage :: (MonadIO m, HasReader SessionContext m, ToJSON a) => a -> m ()
sendMessage msg = do
B.hPut h (addHeader encoded)
--- withTimeout :: Int -> Session a -> Session a
--- withTimeout duration = do
--- liftIO $ fork threadDelay
+-- | Execute a block f that will throw a 'TimeoutException'
+-- after duration seconds. This will override the global timeout
+-- for waiting for messages to arrive defined in 'SessionConfig'.
+withTimeout :: Int -> Session a -> Session a
+withTimeout duration f = do
+ chan <- asks messageChan
+ timeoutId <- curTimeoutId <$> get
+ modify $ \s -> s { overridingTimeout = True }
+ liftIO $ forkIO $ do
+ threadDelay (duration * 1000000)
+ writeChan chan (TimeoutMessage timeoutId)
+ res <- f
+ modify $ \s -> s { curTimeoutId = timeoutId + 1,
+ overridingTimeout = False
+ }
+ return res
sessionConfig = return def
parsingSpec :: Spec
-parsingSpec =
- describe "diagnostics" $ do
- let testDiag = NotPublishDiagnostics
- (NotificationMessage "2.0"
- TextDocumentPublishDiagnostics
- (PublishDiagnosticsParams (Uri "foo")
- (List [])))
- it "get picked up" $ do
- let source = yield testDiag
- session = do
- diags <- publishDiagnosticsNotification :: TestSession PublishDiagnosticsNotification
- return $ diags ^. params . uri
- runConduit (source .| runConduitParser session) `shouldReturn` Uri "foo"
- it "get picked up after skipping others before" $ do
- let testDiag = NotPublishDiagnostics
- (NotificationMessage "2.0"
- TextDocumentPublishDiagnostics
- (PublishDiagnosticsParams (Uri "foo")
- (List [])))
- notTestDiag = NotLogMessage (NotificationMessage "2.0" WindowLogMessage (LogMessageParams MtLog "foo"))
- source = yield notTestDiag >> yield testDiag
- session = do
- diags <- skipManyTill anyNotification notification :: TestSession PublishDiagnosticsNotification
- return $ diags ^. params . uri
- runConduit (source .| runConduitParser session) `shouldReturn` Uri "foo"
\ No newline at end of file
+parsingSpec = return ()
+-- describe "diagnostics" $ do
+-- let testDiag = NotPublishDiagnostics
+-- (NotificationMessage "2.0"
+-- TextDocumentPublishDiagnostics
+-- (PublishDiagnosticsParams (Uri "foo")
+-- (List [])))
+-- it "get picked up" $ do
+-- let source = yield testDiag
+-- session = do
+-- diags <- publishDiagnosticsNotification :: TestSession PublishDiagnosticsNotification
+-- return $ diags ^. params . uri
+-- runConduit (source .| runConduitParser session) `shouldReturn` Uri "foo"
+-- it "get picked up after skipping others before" $ do
+-- let testDiag = NotPublishDiagnostics
+-- (NotificationMessage "2.0"
+-- TextDocumentPublishDiagnostics
+-- (PublishDiagnosticsParams (Uri "foo")
+-- (List [])))
+-- notTestDiag = NotLogMessage (NotificationMessage "2.0" WindowLogMessage (LogMessageParams MtLog "foo"))
+-- source = yield notTestDiag >> yield testDiag
+-- session = do
+-- diags <- skipManyTill anyNotification notification :: TestSession PublishDiagnosticsNotification
+-- return $ diags ^. params . uri
+-- runConduit (source .| runConduitParser session) `shouldReturn` Uri "foo"
import qualified Data.Text as T
import Control.Concurrent
import Control.Monad.IO.Class
+import Control.Monad
import Control.Lens hiding (List)
import GHC.Generics
import Language.Haskell.LSP.Messages
import Language.Haskell.LSP.Test
import Language.Haskell.LSP.Test.Replay
import Language.Haskell.LSP.TH.ClientCapabilities
-import Language.Haskell.LSP.Types hiding (capabilities)
+import Language.Haskell.LSP.Types hiding (message, capabilities)
import ParsingTests
+import System.Timeout
main = hspec $ do
describe "manual session" $ do
conf = def { capabilities = caps }
runSessionWithConfig conf "hie --lsp" "test/data/renamePass" $ return ()
+ describe "withTimeout" $ do
+ it "times out" $
+ let sesh = runSession "hie --lsp" "test/data/renamePass" $ do
+ openDoc "Desktop/simple.hs" "haskell"
+ -- won't receive a request - will timeout
+ -- incoming logging requests shouldn't increase the
+ -- timeout
+ withTimeout 5 $ skipManyTill anyMessage message :: Session ApplyWorkspaceEditRequest
+ -- wait just a bit longer than 5 seconds so we have time
+ -- to open the document
+ in timeout 6000000 sesh `shouldThrow` anySessionException
+
+ it "doesn't time out" $
+ let sesh = runSession "hie --lsp" "test/data/renamePass" $ do
+ openDoc "Desktop/simple.hs" "haskell"
+ withTimeout 5 $ skipManyTill anyMessage publishDiagnosticsNotification
+ in void $ timeout 6000000 sesh
+
+ it "further timeout messages are ignored" $ runSession "hie --lsp" "test/data/renamePass" $ do
+ doc <- openDoc "Desktop/simple.hs" "haskell"
+ withTimeout 3 $ getDocumentSymbols doc
+ liftIO $ threadDelay 5000000
+ -- shouldn't throw an exception
+ getDocumentSymbols doc
+ return ()
+
+ it "overrides global message timeout" $
+ let sesh =
+ runSessionWithConfig (def { messageTimeout = 5 }) "hie --lsp" "test/data/renamePass" $ do
+ doc <- openDoc "Desktop/simple.hs" "haskell"
+ -- shouldn't time out in here since we are overriding it
+ withTimeout 10 $ liftIO $ threadDelay 7000000
+ getDocumentSymbols doc
+ return True
+ in sesh `shouldReturn` True
+
+ it "unoverrides global message timeout" $
+ let sesh =
+ runSessionWithConfig (def { messageTimeout = 5 }) "hie --lsp" "test/data/renamePass" $ do
+ doc <- openDoc "Desktop/simple.hs" "haskell"
+ -- shouldn't time out in here since we are overriding it
+ withTimeout 10 $ liftIO $ threadDelay 7000000
+ getDocumentSymbols doc
+ -- should now timeout
+ skipManyTill anyMessage message :: Session ApplyWorkspaceEditRequest
+ in sesh `shouldThrow` (== TimeoutException)
+
+
describe "exceptions" $ do
it "throw on time out" $
- let sesh = runSessionWithConfig (def {timeout = 10}) "hie --lsp" "test/data/renamePass" $ do
+ let sesh = runSessionWithConfig (def {messageTimeout = 10}) "hie --lsp" "test/data/renamePass" $ do
skipMany loggingNotification
- _ <- request :: Session ApplyWorkspaceEditRequest
+ _ <- message :: Session ApplyWorkspaceEditRequest
return ()
in sesh `shouldThrow` anySessionException
- it "don't throw when no time out" $ runSessionWithConfig (def {timeout = 5}) "hie --lsp" "test/data/renamePass" $ do
+ it "don't throw when no time out" $ runSessionWithConfig (def {messageTimeout = 5}) "hie --lsp" "test/data/renamePass" $ do
loggingNotification
liftIO $ threadDelay 10
_ <- openDoc "Desktop/simple.hs" "haskell"
return ()
- it "throw when there's an unexpected message" $
+ describe "UnexpectedMessageException" $ do
+ it "throws when there's an unexpected message" $
let selector (UnexpectedMessageException "Publish diagnostics notification" (NotLogMessage _)) = True
selector _ = False
in runSession "hie --lsp" "test/data/renamePass" publishDiagnosticsNotification `shouldThrow` selector
-
- it "throw when there's an unexpected message 2" $
- let selector (UnexpectedMessageException "Response" (NotPublishDiagnostics _)) = True
+ it "provides the correct types that were expected and received" $
+ let selector (UnexpectedMessageException "ResponseMessage WorkspaceEdit" (RspDocumentSymbols _)) = True
selector _ = False
sesh = do
doc <- openDoc "Desktop/simple.hs" "haskell"
sendRequest' TextDocumentDocumentSymbol (DocumentSymbolParams doc)
skipMany anyNotification
- response :: Session RenameResponse -- the wrong type
+ message :: Session RenameResponse -- the wrong type
in runSession "hie --lsp" "test/data/renamePass" sesh
`shouldThrow` selector
reqParams = ExecuteCommandParams "applyrefact:applyOne" (Just (List [args]))
sendRequest_ WorkspaceExecuteCommand reqParams
- editReq <- request :: Session ApplyWorkspaceEditRequest
+ editReq <- message :: Session ApplyWorkspaceEditRequest
liftIO $ do
let (Just cs) = editReq ^. params . edit . changes
[(u, List es)] = HM.toList cs
, start_pos :: Position
, hintTitle :: String
} deriving (Generic, ToJSON)
-