# Revision history for lsp-test
+## 0.5.2.0 -- 2019-04-28
+
+* Add `satisfy` parser combinator
+
+## 0.5.1.0 -- 2019-04-22
+
+* Fix unhandled `window/progress` server notifications
+
## 0.5.1.0 -- 2019-04-07
* Add getTypeDefinitions (@fendor)
name: lsp-test
-version: 0.5.1.1
+version: 0.5.2.3
synopsis: Functional test framework for LSP servers.
description:
A test framework for writing tests against
<https://microsoft.github.io/language-server-protocol/ Language Server Protocol servers>.
@Language.Haskell.LSP.Test@ launches your server as a subprocess and allows you to simulate a session
down to the wire, and @Language.Haskell.LSP.Test@ can replay captured sessions from
- <haskell-lsp-test https://hackage.haskell.org/package/haskell-lsp>.
+ <haskell-lsp https://hackage.haskell.org/package/haskell-lsp>.
It's currently used for testing in <https://github.com/haskell/haskell-ide-engine haskell-ide-engine>.
-homepage: https://github.com/Bubba/haskell-lsp-test#readme
+homepage: https://github.com/bubba/lsp-test#readme
license: BSD3
license-file: LICENSE
author: Luke Lau
maintainer: luke_lau@icloud.com
stability: experimental
-bug-reports: https://github.com/Bubba/haskell-lsp-test/issues
+bug-reports: https://github.com/bubba/lsp-test/issues
copyright: 2019 Luke Lau
category: Testing
build-type: Simple
cabal-version: 2.0
extra-source-files: README.md
, ChangeLog.md
-tested-with: GHC == 8.2.2 , GHC == 8.4.2 , GHC == 8.4.3, GHC == 8.6.4
+tested-with: GHC == 8.2.2 , GHC == 8.4.2 , GHC == 8.4.3, GHC == 8.6.4, GHC == 8.6.5
source-repository head
type: git
- location: https://github.com/Bubba/haskell-lsp-test/
+ location: https://github.com/bubba/lsp-test/
library
hs-source-dirs: src
, parser-combinators:Control.Applicative.Combinators
default-language: Haskell2010
build-depends: base >= 4.10 && < 5
- , haskell-lsp >= 0.8 && < 0.10
+ , haskell-lsp >= 0.13.0 && < 0.14
, aeson
, aeson-pretty
, ansi-terminal
, mtl
, parser-combinators
, process
+ , rope-utf16-splay
, text
, transformers
, unordered-containers
- , yi-rope
if os(windows)
build-depends: Win32
else
build-depends: base >= 4.10 && < 5
, hspec
, lens
- , haskell-lsp >= 0.8 && < 0.10
+ , haskell-lsp >= 0.13.0 && < 0.14
, lsp-test
, data-default
, aeson
, initializeResponse
-- ** Documents
, openDoc
+ , openDoc'
, closeDoc
+ , changeDoc
, documentContents
, getDocumentEdit
, getDocUri
import System.IO
import System.Directory
import System.FilePath
-import qualified Yi.Rope as Rope
+import qualified Data.Rope.UTF16 as Rope
-- | Starts a new session.
--
modify (\s -> s { vfs = newVFS })
sendMessage n
+sendNotification TextDocumentDidChange params = do
+ let params' = fromJust $ decode $ encode params
+ n :: DidChangeTextDocumentNotification
+ n = NotificationMessage "2.0" TextDocumentDidChange params'
+ oldVFS <- vfs <$> get
+ newVFS <- liftIO $ changeFromClientVFS oldVFS n
+ modify (\s -> s { vfs = newVFS })
+ sendMessage n
+
sendNotification method params = sendMessage (NotificationMessage "2.0" method params)
-- | Sends a response to the server.
-- | Opens a text document and sends a notification to the client.
openDoc :: FilePath -> String -> Session TextDocumentIdentifier
openDoc file languageId = do
- item <- getDocItem file languageId
- sendNotification TextDocumentDidOpen (DidOpenTextDocumentParams item)
- TextDocumentIdentifier <$> getDocUri file
- where
- -- | Reads in a text document as the first version.
- getDocItem :: FilePath -- ^ The path to the text document to read in.
- -> String -- ^ The language ID, e.g "haskell" for .hs files.
- -> Session TextDocumentItem
- getDocItem file languageId = do
context <- ask
let fp = rootDir context </> file
contents <- liftIO $ T.readFile fp
- return $ TextDocumentItem (filePathToUri fp) (T.pack languageId) 0 contents
+ openDoc' file languageId contents
+
+-- | This is a variant of `openDoc` that takes the file content as an argument.
+openDoc' :: FilePath -> String -> T.Text -> Session TextDocumentIdentifier
+openDoc' file languageId contents = do
+ context <- ask
+ let fp = rootDir context </> file
+ uri = filePathToUri fp
+ item = TextDocumentItem uri (T.pack languageId) 0 contents
+ sendNotification TextDocumentDidOpen (DidOpenTextDocumentParams item)
+ pure $ TextDocumentIdentifier uri
-- | Closes a text document and sends a notification to the client.
closeDoc :: TextDocumentIdentifier -> Session ()
let params = DidCloseTextDocumentParams (TextDocumentIdentifier (docId ^. uri))
sendNotification TextDocumentDidClose params
- oldVfs <- vfs <$> get
- let notif = NotificationMessage "" TextDocumentDidClose params
- newVfs <- liftIO $ closeVFS oldVfs notif
- modify $ \s -> s { vfs = newVfs }
+-- | Changes a text document and sends a notification to the client
+changeDoc :: TextDocumentIdentifier -> [TextDocumentContentChangeEvent] -> Session ()
+changeDoc docId changes = do
+ verDoc <- getVersionedDoc docId
+ let params = DidChangeTextDocumentParams (verDoc & version . non 0 +~ 1) (List changes)
+ sendNotification TextDocumentDidChange params
-- | Gets the Uri for the file corrected to the session directory.
getDocUri :: FilePath -> Session Uri
matches d = d ^. source == Just (T.pack src)
-- | Expects a 'PublishDiagnosticsNotification' and throws an
--- 'UnexpectedDiagnosticsException' if there are any diagnostics
+-- 'UnexpectedDiagnostics' exception if there are any diagnostics
-- returned.
noDiagnostics :: Session ()
noDiagnostics = do
fs <- vfs <$> get
let ver =
case fs Map.!? uri of
- Just (VirtualFile v _) -> Just v
+ Just (VirtualFile v _ _) -> Just v
_ -> Nothing
return (VersionedTextDocumentIdentifier uri ver)
caps <- asks sessionCapabilities
let supportsDocChanges = fromMaybe False $ do
- let C.ClientCapabilities mWorkspace _ _ = caps
+ let mWorkspace = C._workspace caps
C.WorkspaceClientCapabilities _ mEdit _ _ _ _ _ _ <- mWorkspace
C.WorkspaceEditClientCapabilities mDocChanges <- mEdit
mDocChanges
WindowShowMessage -> NotShowMessage $ fromJust $ decode bytes
WindowLogMessage -> NotLogMessage $ fromJust $ decode bytes
CancelRequestServer -> NotCancelRequestFromServer $ fromJust $ decode bytes
+ WindowProgressStart -> NotProgressStart $ fromJust $ decode bytes
+ WindowProgressReport -> NotProgressReport $ fromJust $ decode bytes
+ WindowProgressDone -> NotProgressDone $ fromJust $ decode bytes
TelemetryEvent -> NotTelemetry $ fromJust $ decode bytes
WindowShowMessageRequest -> ReqShowMessage $ fromJust $ decode bytes
ClientRegisterCapability -> ReqRegisterCapability $ fromJust $ decode bytes
(NotPublishDiagnostics m) -> notification m
(NotLogMessage m) -> notification m
(NotShowMessage m) -> notification m
+ (NotProgressStart m) -> notification m
+ (NotProgressReport m) -> notification m
+ (NotProgressDone m) -> notification m
(NotTelemetry m) -> notification m
(NotCancelRequestFromServer m) -> notification m
(NotDidSaveTextDocument m) -> notification m
(NotDidChangeWatchedFiles m) -> notification m
(NotDidChangeWorkspaceFolders m) -> notification m
+ (NotProgressCancel m) -> notification m
(UnknownFromClientMessage m) -> error $ "Unknown message sent from client: " ++ show m
module Language.Haskell.LSP.Test.Parsing
( -- $receiving
- message
+ satisfy
+ , message
, anyRequest
, anyResponse
, anyNotification
-- anyResponse
-- @
+-- | Consumes and returns the next message, if it satisfies the specified predicate.
+--
+-- @since 0.5.2.0
satisfy :: (FromServerMessage -> Bool) -> Session FromServerMessage
satisfy pred = do
return x
else empty
--- | Matches a message of type 'a'.
+-- | Matches a message of type @a@.
message :: forall a. (Typeable a, FromJSON a) => Session a
message =
let parser = decode . encodeMsg :: FromServerMessage -> Maybe a
-- | A session representing one instance of launching and connecting to a server.
--
--- You can send and receive messages to the server within 'Session' via 'getMessage',
--- 'sendRequest' and 'sendNotification'.
---
+-- You can send and receive messages to the server within 'Session' via
+-- 'Language.Haskell.LSP.Test.message',
+-- 'Language.Haskell.LSP.Test.sendRequest' and
+-- 'Language.Haskell.LSP.Test.sendNotification'.
type Session = ParserStateReader FromServerMessage SessionState SessionContext IO
forM_ bumpedVersions $ \(VersionedTextDocumentIdentifier uri v) ->
modify $ \s ->
let oldVFS = vfs s
- update (VirtualFile oldV t) = VirtualFile (fromMaybe oldV v) t
+ update (VirtualFile oldV t mf) = VirtualFile (fromMaybe oldV v) t mf
newVFS = Map.adjust update uri oldVFS
in s { vfs = newVFS }
logMsg LogClient msg
liftIO $ B.hPut h (addHeader $ encode msg)
--- | Execute a block f that will throw a 'TimeoutException'
+-- | Execute a block f that will throw a 'Timeout' exception
-- after duration seconds. This will override the global timeout
-- for waiting for messages to arrive defined in 'SessionConfig'.
withTimeout :: Int -> Session a -> Session a
-resolver: lts-13.15
+resolver: lts-13.21
packages:
- .
+
extra-deps:
-- haskell-lsp-0.9.0.0
-- haskell-lsp-types-0.9.0.0
+ - haskell-lsp-0.13.0.0
+ - haskell-lsp-types-0.13.0.0
+ - rope-utf16-splay-0.3.1.0
documentContents doc >>= liftIO . print
in sesh `shouldThrow` anyException
+ describe "satisfy" $
+ it "works" $ runSession "hie" fullCaps "test/data" $ do
+ openDoc "Format.hs" "haskell"
+ let pred (NotLogMessage _) = True
+ pred _ = False
+ void $ satisfy pred
+
mkRange sl sc el ec = Range (Position sl sc) (Position el ec)
didChangeCaps :: ClientCapabilities