-- * Sessions
runSession
, runSessionWithHandles
+ , runSessionWithConfig
, Session
+ , SessionConfig(..)
+ , MonadSessionConfig(..)
+ , SessionException(..)
+ , anySessionException
-- * Sending
, sendRequest
, sendNotification
, (<|>)
, satisfy
-- * Utilities
- , getInitializeResponse
+ , initializeResponse
, openDoc
- , getDocItem
, documentContents
+ , documentEdit
, getDocUri
+ , noDiagnostics
+ , documentSymbols
+ ,
) where
import Control.Applicative
import Control.Applicative.Combinators
-import Control.Monad.IO.Class
import Control.Concurrent
+import Control.Monad
+import Control.Monad.IO.Class
+import Control.Exception
import Control.Lens hiding ((.=), List)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Data.Aeson
import qualified Data.ByteString.Lazy.Char8 as B
import Data.Default
+import qualified Data.HashMap.Strict as HashMap
import qualified Data.Map as Map
import Data.Maybe
-import Language.Haskell.LSP.Types
-import qualified Language.Haskell.LSP.Types as LSP (error, id)
+import Language.Haskell.LSP.Types hiding (id, capabilities)
+import qualified Language.Haskell.LSP.Types as LSP
import Language.Haskell.LSP.VFS
import Language.Haskell.LSP.Test.Compat
import Language.Haskell.LSP.Test.Decoding
+import Language.Haskell.LSP.Test.Exceptions
import Language.Haskell.LSP.Test.Parsing
import Language.Haskell.LSP.Test.Session
import Language.Haskell.LSP.Test.Server
-> FilePath -- ^ The filepath to the root directory for the session.
-> Session a -- ^ The session to run.
-> IO a
-runSession serverExe rootDir session = do
- pid <- getProcessID
+runSession = runSessionWithConfig def
+
+-- | Starts a new sesion with a client with the specified capabilities.
+runSessionWithConfig :: SessionConfig -- ^ The capabilities the client should have.
+ -> String -- ^ The command to run the server.
+ -> FilePath -- ^ The filepath to the root directory for the session.
+ -> Session a -- ^ The session to run.
+ -> IO a
+runSessionWithConfig config serverExe rootDir session = do
+ pid <- getCurrentProcessID
absRootDir <- canonicalizePath rootDir
let initializeParams = InitializeParams (Just pid)
(Just $ T.pack absRootDir)
(Just $ filePathToUri absRootDir)
Nothing
- def
+ (capabilities config)
(Just TraceOff)
- withServer serverExe $ \serverIn serverOut _ -> runSessionWithHandles serverIn serverOut listenServer rootDir $ do
+ withServer serverExe $ \serverIn serverOut _ ->
+ runSessionWithHandles serverIn serverOut listenServer config rootDir $ do
-- Wrap the session around initialize and shutdown calls
sendRequest Initialize initializeParams
let file = vfs Map.! (doc ^. uri)
return $ Rope.toText $ Language.Haskell.LSP.VFS._text file
+-- | Parses an ApplyEditRequest, checks that it is for the passed document
+-- and returns the new content
+documentEdit :: TextDocumentIdentifier -> Session T.Text
+documentEdit doc = do
+ req <- request :: Session ApplyWorkspaceEditRequest
+
+ unless (checkDocumentChanges req || checkChanges req) $
+ liftIO $ throw (IncorrectApplyEditRequestException (show req))
+
+ documentContents doc
+ where
+ checkDocumentChanges :: ApplyWorkspaceEditRequest -> Bool
+ checkDocumentChanges req =
+ let changes = req ^. params . edit . documentChanges
+ maybeDocs = fmap (fmap (^. textDocument . uri)) changes
+ in case maybeDocs of
+ Just docs -> (doc ^. uri) `elem` docs
+ Nothing -> False
+ checkChanges :: ApplyWorkspaceEditRequest -> Bool
+ checkChanges req =
+ let mMap = req ^. params . edit . changes
+ in maybe False (HashMap.member (doc ^. uri)) mMap
+
-- | Sends a request to the server.
--
-- @
modify (\s -> s { vfs = newVFS })
sendNotification' n
+-- | Close a virtual file if we send a close text document notification
+sendNotification TextDocumentDidClose params = do
+ let params' = fromJust $ decode $ encode params
+ n :: DidCloseTextDocumentNotification
+ n = NotificationMessage "2.0" TextDocumentDidClose params'
+ oldVFS <- vfs <$> get
+ newVFS <- liftIO $ closeVFS oldVFS n
+ modify (\s -> s { vfs = newVFS })
+ sendNotification' n
+
sendNotification method params = sendNotification' (NotificationMessage "2.0" method params)
sendNotification' :: (ToJSON a, ToJSON b) => NotificationMessage a b -> Session ()
-- | Returns the initialize response that was received from the server.
-- The initialize requests and responses are not included the session,
-- so if you need to test it use this.
-getInitializeResponse :: Session InitializeResponse
-getInitializeResponse = initRsp <$> ask >>= (liftIO . readMVar)
+initializeResponse :: Session InitializeResponse
+initializeResponse = initRsp <$> ask >>= (liftIO . readMVar)
-- | Opens a text document and sends a notification to the client.
openDoc :: FilePath -> String -> Session TextDocumentIdentifier
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.
let fp = rootDir context </> file
return $ filePathToUri fp
+-- | Expects a 'PublishDiagnosticsNotification' and throws an
+-- 'UnexpectedDiagnosticsException' if there are any diagnostics
+-- returned.
+noDiagnostics :: Session ()
+noDiagnostics = do
+ diagsNot <- notification :: Session PublishDiagnosticsNotification
+ when (diagsNot ^. params . diagnostics /= List []) $ liftIO $ throw UnexpectedDiagnosticsException
+
+-- | Returns the symbols in a document.
+documentSymbols :: TextDocumentIdentifier -> Session DocumentSymbolsResponse
+documentSymbols doc = do
+ sendRequest TextDocumentDocumentSymbol (DocumentSymbolParams doc)
+ response
\ No newline at end of file