Don't use exitServer in Replay
[lsp-test.git] / src / Language / Haskell / LSP / Test.hs
1 {-# LANGUAGE OverloadedStrings #-}
2 {-# LANGUAGE RankNTypes #-}
3 {-# LANGUAGE ScopedTypeVariables #-}
4 {-# LANGUAGE ExistentialQuantification #-}
5
6 {-|
7 Module      : Language.Haskell.LSP.Test
8 Description : A functional testing framework for LSP servers.
9 Maintainer  : luke_lau@icloud.com
10 Stability   : experimental
11 Portability : POSIX
12
13 Provides the framework to start functionally testing
14 <https://github.com/Microsoft/language-server-protocol Language Server Protocol servers>.
15 You should import "Language.Haskell.LSP.Types" alongside this.
16 -}
17 module Language.Haskell.LSP.Test
18   (
19   -- * Sessions
20     Session
21   , runSession
22   -- ** Config
23   , runSessionWithConfig
24   , SessionConfig(..)
25   , defaultConfig
26   , C.fullCaps
27   -- ** Exceptions
28   , module Language.Haskell.LSP.Test.Exceptions
29   , withTimeout
30   -- * Sending
31   , request
32   , request_
33   , sendRequest
34   , sendNotification
35   , sendResponse
36   -- * Receving
37   , module Language.Haskell.LSP.Test.Parsing
38   -- * Utilities
39   -- | Quick helper functions for common tasks.
40   -- ** Initialization
41   , initializeResponse
42   -- ** Documents
43   , openDoc
44   , openDoc'
45   , closeDoc
46   , changeDoc
47   , documentContents
48   , getDocumentEdit
49   , getDocUri
50   , getVersionedDoc
51   -- ** Symbols
52   , getDocumentSymbols
53   -- ** Diagnostics
54   , waitForDiagnostics
55   , waitForDiagnosticsSource
56   , noDiagnostics
57   , getCurrentDiagnostics
58   -- ** Commands
59   , executeCommand
60   -- ** Code Actions
61   , getCodeActions
62   , getAllCodeActions
63   , executeCodeAction
64   -- ** Completions
65   , getCompletions
66   -- ** References
67   , getReferences
68   -- ** Definitions
69   , getDefinitions
70   , getTypeDefinitions
71   -- ** Renaming
72   , rename
73   -- ** Hover
74   , getHover
75   -- ** Highlights
76   , getHighlights
77   -- ** Formatting
78   , formatDoc
79   , formatRange
80   -- ** Edits
81   , applyEdit
82   -- ** Code lenses
83   , getCodeLenses
84   ) where
85
86 import Control.Applicative.Combinators
87 import Control.Concurrent
88 import Control.Monad
89 import Control.Monad.IO.Class
90 import Control.Exception
91 import Control.Lens hiding ((.=), List)
92 import qualified Data.Text as T
93 import qualified Data.Text.IO as T
94 import Data.Aeson
95 import Data.Default
96 import qualified Data.HashMap.Strict as HashMap
97 import qualified Data.Map as Map
98 import Data.Maybe
99 import Language.Haskell.LSP.Types
100 import Language.Haskell.LSP.Types.Lens hiding
101   (id, capabilities, message, executeCommand, applyEdit, rename)
102 import qualified Language.Haskell.LSP.Types.Lens as LSP
103 import qualified Language.Haskell.LSP.Types.Capabilities as C
104 import Language.Haskell.LSP.Messages
105 import Language.Haskell.LSP.VFS
106 import Language.Haskell.LSP.Test.Compat
107 import Language.Haskell.LSP.Test.Decoding
108 import Language.Haskell.LSP.Test.Exceptions
109 import Language.Haskell.LSP.Test.Parsing
110 import Language.Haskell.LSP.Test.Session
111 import Language.Haskell.LSP.Test.Server
112 import System.IO
113 import System.Directory
114 import System.FilePath
115 import qualified Data.Rope.UTF16 as Rope
116
117 -- | Starts a new session.
118 --
119 -- > runSession "hie" fullCaps "path/to/root/dir" $ do
120 -- >   doc <- openDoc "Desktop/simple.hs" "haskell"
121 -- >   diags <- waitForDiagnostics
122 -- >   let pos = Position 12 5
123 -- >       params = TextDocumentPositionParams doc
124 -- >   hover <- request TextDocumentHover params
125 runSession :: String -- ^ The command to run the server.
126            -> C.ClientCapabilities -- ^ The capabilities that the client should declare.
127            -> FilePath -- ^ The filepath to the root directory for the session.
128            -> Session a -- ^ The session to run.
129            -> IO a
130 runSession = runSessionWithConfig def
131
132 -- | Starts a new sesion with a custom configuration.
133 runSessionWithConfig :: SessionConfig -- ^ Configuration options for the session.
134                      -> String -- ^ The command to run the server.
135                      -> C.ClientCapabilities -- ^ The capabilities that the client should declare.
136                      -> FilePath -- ^ The filepath to the root directory for the session.
137                      -> Session a -- ^ The session to run.
138                      -> IO a
139 runSessionWithConfig config serverExe caps rootDir session = do
140   pid <- getCurrentProcessID
141   absRootDir <- canonicalizePath rootDir
142
143   let initializeParams = InitializeParams (Just pid)
144                                           (Just $ T.pack absRootDir)
145                                           (Just $ filePathToUri absRootDir)
146                                           Nothing
147                                           caps
148                                           (Just TraceOff)
149                                           Nothing
150   withServer serverExe (logStdErr config) $ \serverIn serverOut serverProc ->
151     runSessionWithHandles serverIn serverOut serverProc listenServer config caps rootDir exitServer $ do
152       -- Wrap the session around initialize and shutdown calls
153       initRspMsg <- request Initialize initializeParams :: Session InitializeResponse
154
155       liftIO $ maybe (return ()) (putStrLn . ("Error while initializing: " ++) . show ) (initRspMsg ^. LSP.error)
156
157       initRspVar <- initRsp <$> ask
158       liftIO $ putMVar initRspVar initRspMsg
159       sendNotification Initialized InitializedParams
160
161       case lspConfig config of
162         Just cfg -> sendNotification WorkspaceDidChangeConfiguration (DidChangeConfigurationParams cfg)
163         Nothing -> return ()
164
165       -- Run the actual test
166       result <- session
167       return result
168   where
169   -- | Asks the server to shutdown and exit politely
170   exitServer :: Session ()
171   exitServer = request_ Shutdown (Nothing :: Maybe Value) >> sendNotification Exit ExitParams
172
173   -- | Listens to the server output until the shutdown ack,
174   -- makes sure it matches the record and signals any semaphores
175   listenServer :: Handle -> SessionContext -> IO ()
176   listenServer serverOut context = do
177     msgBytes <- getNextMessage serverOut
178
179     reqMap <- readMVar $ requestMap context
180
181     let msg = decodeFromServerMsg reqMap msgBytes
182     writeChan (messageChan context) (ServerMessage msg)
183
184     case msg of
185       (RspShutdown _) -> return ()
186       _               -> listenServer serverOut context
187
188 -- | The current text contents of a document.
189 documentContents :: TextDocumentIdentifier -> Session T.Text
190 documentContents doc = do
191   vfs <- vfs <$> get
192   let file = vfs Map.! toNormalizedUri (doc ^. uri)
193   return $ Rope.toText $ Language.Haskell.LSP.VFS._text file
194
195 -- | Parses an ApplyEditRequest, checks that it is for the passed document
196 -- and returns the new content
197 getDocumentEdit :: TextDocumentIdentifier -> Session T.Text
198 getDocumentEdit doc = do
199   req <- message :: Session ApplyWorkspaceEditRequest
200
201   unless (checkDocumentChanges req || checkChanges req) $
202     liftIO $ throw (IncorrectApplyEditRequest (show req))
203
204   documentContents doc
205   where
206     checkDocumentChanges :: ApplyWorkspaceEditRequest -> Bool
207     checkDocumentChanges req =
208       let changes = req ^. params . edit . documentChanges
209           maybeDocs = fmap (fmap (^. textDocument . uri)) changes
210       in case maybeDocs of
211         Just docs -> (doc ^. uri) `elem` docs
212         Nothing -> False
213     checkChanges :: ApplyWorkspaceEditRequest -> Bool
214     checkChanges req =
215       let mMap = req ^. params . edit . changes
216         in maybe False (HashMap.member (doc ^. uri)) mMap
217
218 -- | Sends a request to the server and waits for its response.
219 -- Will skip any messages in between the request and the response
220 -- @
221 -- rsp <- request TextDocumentDocumentSymbol params :: Session DocumentSymbolsResponse
222 -- @
223 -- Note: will skip any messages in between the request and the response.
224 request :: (ToJSON params, FromJSON a) => ClientMethod -> params -> Session (ResponseMessage a)
225 request m = sendRequest m >=> skipManyTill anyMessage . responseForId
226
227 -- | The same as 'sendRequest', but discard the response.
228 request_ :: ToJSON params => ClientMethod -> params -> Session ()
229 request_ p = void . (request p :: ToJSON params => params -> Session (ResponseMessage Value))
230
231 -- | Sends a request to the server. Unlike 'request', this doesn't wait for the response.
232 sendRequest
233   :: ToJSON params
234   => ClientMethod -- ^ The request method.
235   -> params -- ^ The request parameters.
236   -> Session LspId -- ^ The id of the request that was sent.
237 sendRequest method params = do
238   id <- curReqId <$> get
239   modify $ \c -> c { curReqId = nextId id }
240
241   let req = RequestMessage' "2.0" id method params
242
243   -- Update the request map
244   reqMap <- requestMap <$> ask
245   liftIO $ modifyMVar_ reqMap $
246     \r -> return $ updateRequestMap r id method
247
248   sendMessage req
249
250   return id
251
252   where nextId (IdInt i) = IdInt (i + 1)
253         nextId (IdString s) = IdString $ T.pack $ show $ read (T.unpack s) + 1
254
255 -- | A custom type for request message that doesn't
256 -- need a response type, allows us to infer the request
257 -- message type without using proxies.
258 data RequestMessage' a = RequestMessage' T.Text LspId ClientMethod a
259
260 instance ToJSON a => ToJSON (RequestMessage' a) where
261   toJSON (RequestMessage' rpc id method params) =
262     object ["jsonrpc" .= rpc, "id" .= id, "method" .= method, "params" .= params]
263
264
265 -- | Sends a notification to the server.
266 sendNotification :: ToJSON a
267                  => ClientMethod -- ^ The notification method.
268                  -> a -- ^ The notification parameters.
269                  -> Session ()
270
271 -- Open a virtual file if we send a did open text document notification
272 sendNotification TextDocumentDidOpen params = do
273   let params' = fromJust $ decode $ encode params
274       n :: DidOpenTextDocumentNotification
275       n = NotificationMessage "2.0" TextDocumentDidOpen params'
276   oldVFS <- vfs <$> get
277   newVFS <- liftIO $ openVFS oldVFS n
278   modify (\s -> s { vfs = newVFS })
279   sendMessage n
280
281 -- Close a virtual file if we send a close text document notification
282 sendNotification TextDocumentDidClose params = do
283   let params' = fromJust $ decode $ encode params
284       n :: DidCloseTextDocumentNotification
285       n = NotificationMessage "2.0" TextDocumentDidClose params'
286   oldVFS <- vfs <$> get
287   newVFS <- liftIO $ closeVFS oldVFS n
288   modify (\s -> s { vfs = newVFS })
289   sendMessage n
290
291 sendNotification TextDocumentDidChange params = do
292     let params' = fromJust $ decode $ encode params
293         n :: DidChangeTextDocumentNotification
294         n = NotificationMessage "2.0" TextDocumentDidChange params'
295     oldVFS <- vfs <$> get
296     newVFS <- liftIO $ changeFromClientVFS oldVFS n
297     modify (\s -> s { vfs = newVFS })
298     sendMessage n
299
300 sendNotification method params = sendMessage (NotificationMessage "2.0" method params)
301
302 -- | Sends a response to the server.
303 sendResponse :: ToJSON a => ResponseMessage a -> Session ()
304 sendResponse = sendMessage
305
306 -- | Returns the initialize response that was received from the server.
307 -- The initialize requests and responses are not included the session,
308 -- so if you need to test it use this.
309 initializeResponse :: Session InitializeResponse
310 initializeResponse = initRsp <$> ask >>= (liftIO . readMVar)
311
312 -- | Opens a text document and sends a notification to the client.
313 openDoc :: FilePath -> String -> Session TextDocumentIdentifier
314 openDoc file languageId = do
315   context <- ask
316   let fp = rootDir context </> file
317   contents <- liftIO $ T.readFile fp
318   openDoc' file languageId contents
319
320 -- | This is a variant of `openDoc` that takes the file content as an argument.
321 openDoc' :: FilePath -> String -> T.Text -> Session TextDocumentIdentifier
322 openDoc' file languageId contents = do
323   context <- ask
324   let fp = rootDir context </> file
325       uri = filePathToUri fp
326       item = TextDocumentItem uri (T.pack languageId) 0 contents
327   sendNotification TextDocumentDidOpen (DidOpenTextDocumentParams item)
328   pure $ TextDocumentIdentifier uri
329
330 -- | Closes a text document and sends a notification to the client.
331 closeDoc :: TextDocumentIdentifier -> Session ()
332 closeDoc docId = do
333   let params = DidCloseTextDocumentParams (TextDocumentIdentifier (docId ^. uri))
334   sendNotification TextDocumentDidClose params
335
336 -- | Changes a text document and sends a notification to the client
337 changeDoc :: TextDocumentIdentifier -> [TextDocumentContentChangeEvent] -> Session ()
338 changeDoc docId changes = do
339   verDoc <- getVersionedDoc docId
340   let params = DidChangeTextDocumentParams (verDoc & version . non 0 +~ 1) (List changes)
341   sendNotification TextDocumentDidChange params
342
343 -- | Gets the Uri for the file corrected to the session directory.
344 getDocUri :: FilePath -> Session Uri
345 getDocUri file = do
346   context <- ask
347   let fp = rootDir context </> file
348   return $ filePathToUri fp
349
350 -- | Waits for diagnostics to be published and returns them.
351 waitForDiagnostics :: Session [Diagnostic]
352 waitForDiagnostics = do
353   diagsNot <- skipManyTill anyMessage message :: Session PublishDiagnosticsNotification
354   let (List diags) = diagsNot ^. params . LSP.diagnostics
355   return diags
356
357 -- | The same as 'waitForDiagnostics', but will only match a specific
358 -- 'Language.Haskell.LSP.Types._source'.
359 waitForDiagnosticsSource :: String -> Session [Diagnostic]
360 waitForDiagnosticsSource src = do
361   diags <- waitForDiagnostics
362   let res = filter matches diags
363   if null res
364     then waitForDiagnosticsSource src
365     else return res
366   where
367     matches :: Diagnostic -> Bool
368     matches d = d ^. source == Just (T.pack src)
369
370 -- | Expects a 'PublishDiagnosticsNotification' and throws an
371 -- 'UnexpectedDiagnostics' exception if there are any diagnostics
372 -- returned.
373 noDiagnostics :: Session ()
374 noDiagnostics = do
375   diagsNot <- message :: Session PublishDiagnosticsNotification
376   when (diagsNot ^. params . LSP.diagnostics /= List []) $ liftIO $ throw UnexpectedDiagnostics
377
378 -- | Returns the symbols in a document.
379 getDocumentSymbols :: TextDocumentIdentifier -> Session (Either [DocumentSymbol] [SymbolInformation])
380 getDocumentSymbols doc = do
381   ResponseMessage _ rspLid mRes mErr <- request TextDocumentDocumentSymbol (DocumentSymbolParams doc) :: Session DocumentSymbolsResponse
382   maybe (return ()) (throw . UnexpectedResponseError rspLid) mErr
383   case mRes of
384     Just (DSDocumentSymbols (List xs)) -> return (Left xs)
385     Just (DSSymbolInformation (List xs)) -> return (Right xs)
386     Nothing -> Prelude.error "No result and no error in DocumentSymbolsResponse"
387
388 -- | Returns the code actions in the specified range.
389 getCodeActions :: TextDocumentIdentifier -> Range -> Session [CAResult]
390 getCodeActions doc range = do
391   ctx <- getCodeActionContext doc
392   rsp <- request TextDocumentCodeAction (CodeActionParams doc range ctx)
393
394   case rsp ^. result of
395     Just (List xs) -> return xs
396     _ -> throw (UnexpectedResponseError (rsp ^. LSP.id) (fromJust $ rsp ^. LSP.error))
397
398 -- | Returns all the code actions in a document by
399 -- querying the code actions at each of the current
400 -- diagnostics' positions.
401 getAllCodeActions :: TextDocumentIdentifier -> Session [CAResult]
402 getAllCodeActions doc = do
403   ctx <- getCodeActionContext doc
404
405   foldM (go ctx) [] =<< getCurrentDiagnostics doc
406
407   where
408     go :: CodeActionContext -> [CAResult] -> Diagnostic -> Session [CAResult]
409     go ctx acc diag = do
410       ResponseMessage _ rspLid mRes mErr <- request TextDocumentCodeAction (CodeActionParams doc (diag ^. range) ctx)
411
412       case mErr of
413         Just e -> throw (UnexpectedResponseError rspLid e)
414         Nothing ->
415           let Just (List cmdOrCAs) = mRes
416             in return (acc ++ cmdOrCAs)
417
418 getCodeActionContext :: TextDocumentIdentifier -> Session CodeActionContext
419 getCodeActionContext doc = do
420   curDiags <- getCurrentDiagnostics doc
421   return $ CodeActionContext (List curDiags) Nothing
422
423 -- | Returns the current diagnostics that have been sent to the client.
424 -- Note that this does not wait for more to come in.
425 getCurrentDiagnostics :: TextDocumentIdentifier -> Session [Diagnostic]
426 getCurrentDiagnostics doc = fromMaybe [] . Map.lookup (toNormalizedUri $ doc ^. uri) . curDiagnostics <$> get
427
428 -- | Executes a command.
429 executeCommand :: Command -> Session ()
430 executeCommand cmd = do
431   let args = decode $ encode $ fromJust $ cmd ^. arguments
432       execParams = ExecuteCommandParams (cmd ^. command) args
433   request_ WorkspaceExecuteCommand execParams
434
435 -- | Executes a code action.
436 -- Matching with the specification, if a code action
437 -- contains both an edit and a command, the edit will
438 -- be applied first.
439 executeCodeAction :: CodeAction -> Session ()
440 executeCodeAction action = do
441   maybe (return ()) handleEdit $ action ^. edit
442   maybe (return ()) executeCommand $ action ^. command
443
444   where handleEdit :: WorkspaceEdit -> Session ()
445         handleEdit e =
446           -- Its ok to pass in dummy parameters here as they aren't used
447           let req = RequestMessage "" (IdInt 0) WorkspaceApplyEdit (ApplyWorkspaceEditParams e)
448             in updateState (ReqApplyWorkspaceEdit req)
449
450 -- | Adds the current version to the document, as tracked by the session.
451 getVersionedDoc :: TextDocumentIdentifier -> Session VersionedTextDocumentIdentifier
452 getVersionedDoc (TextDocumentIdentifier uri) = do
453   fs <- vfs <$> get
454   let ver =
455         case fs Map.!? toNormalizedUri uri of
456           Just (VirtualFile v _ _) -> Just v
457           _ -> Nothing
458   return (VersionedTextDocumentIdentifier uri ver)
459
460 -- | Applys an edit to the document and returns the updated document version.
461 applyEdit :: TextDocumentIdentifier -> TextEdit -> Session VersionedTextDocumentIdentifier
462 applyEdit doc edit = do
463
464   verDoc <- getVersionedDoc doc
465
466   caps <- asks sessionCapabilities
467
468   let supportsDocChanges = fromMaybe False $ do
469         let mWorkspace = C._workspace caps
470         C.WorkspaceClientCapabilities _ mEdit _ _ _ _ _ _ <- mWorkspace
471         C.WorkspaceEditClientCapabilities mDocChanges <- mEdit
472         mDocChanges
473
474   let wEdit = if supportsDocChanges
475       then
476         let docEdit = TextDocumentEdit verDoc (List [edit])
477         in WorkspaceEdit Nothing (Just (List [docEdit]))
478       else
479         let changes = HashMap.singleton (doc ^. uri) (List [edit])
480         in WorkspaceEdit (Just changes) Nothing
481
482   let req = RequestMessage "" (IdInt 0) WorkspaceApplyEdit (ApplyWorkspaceEditParams wEdit)
483   updateState (ReqApplyWorkspaceEdit req)
484
485   -- version may have changed
486   getVersionedDoc doc
487
488 -- | Returns the completions for the position in the document.
489 getCompletions :: TextDocumentIdentifier -> Position -> Session [CompletionItem]
490 getCompletions doc pos = do
491   rsp <- request TextDocumentCompletion (TextDocumentPositionParams doc pos)
492
493   case getResponseResult rsp of
494     Completions (List items) -> return items
495     CompletionList (CompletionListType _ (List items)) -> return items
496
497 -- | Returns the references for the position in the document.
498 getReferences :: TextDocumentIdentifier -- ^ The document to lookup in.
499               -> Position -- ^ The position to lookup.
500               -> Bool -- ^ Whether to include declarations as references.
501               -> Session [Location] -- ^ The locations of the references.
502 getReferences doc pos inclDecl =
503   let ctx = ReferenceContext inclDecl
504       params = ReferenceParams doc pos ctx
505   in getResponseResult <$> request TextDocumentReferences params
506
507 -- | Returns the definition(s) for the term at the specified position.
508 getDefinitions :: TextDocumentIdentifier -- ^ The document the term is in.
509                -> Position -- ^ The position the term is at.
510                -> Session [Location] -- ^ The location(s) of the definitions
511 getDefinitions doc pos = do
512   let params = TextDocumentPositionParams doc pos
513   rsp <- request TextDocumentDefinition params :: Session DefinitionResponse
514   case getResponseResult rsp of
515       SingleLoc loc -> pure [loc]
516       MultiLoc locs -> pure locs
517
518 -- | Returns the type definition(s) for the term at the specified position.
519 getTypeDefinitions :: TextDocumentIdentifier -- ^ The document the term is in.
520                -> Position -- ^ The position the term is at.
521                -> Session [Location] -- ^ The location(s) of the definitions
522 getTypeDefinitions doc pos =
523   let params = TextDocumentPositionParams doc pos
524   in getResponseResult <$> request TextDocumentTypeDefinition params
525
526 -- | Renames the term at the specified position.
527 rename :: TextDocumentIdentifier -> Position -> String -> Session ()
528 rename doc pos newName = do
529   let params = RenameParams doc pos (T.pack newName)
530   rsp <- request TextDocumentRename params :: Session RenameResponse
531   let wEdit = getResponseResult rsp
532       req = RequestMessage "" (IdInt 0) WorkspaceApplyEdit (ApplyWorkspaceEditParams wEdit)
533   updateState (ReqApplyWorkspaceEdit req)
534
535 -- | Returns the hover information at the specified position.
536 getHover :: TextDocumentIdentifier -> Position -> Session (Maybe Hover)
537 getHover doc pos =
538   let params = TextDocumentPositionParams doc pos
539   in getResponseResult <$> request TextDocumentHover params
540
541 -- | Returns the highlighted occurences of the term at the specified position
542 getHighlights :: TextDocumentIdentifier -> Position -> Session [DocumentHighlight]
543 getHighlights doc pos =
544   let params = TextDocumentPositionParams doc pos
545   in getResponseResult <$> request TextDocumentDocumentHighlight params
546
547 -- | Checks the response for errors and throws an exception if needed.
548 -- Returns the result if successful.
549 getResponseResult :: ResponseMessage a -> a
550 getResponseResult rsp = fromMaybe exc (rsp ^. result)
551   where exc = throw $ UnexpectedResponseError (rsp ^. LSP.id)
552                                               (fromJust $ rsp ^. LSP.error)
553
554 -- | Applies formatting to the specified document.
555 formatDoc :: TextDocumentIdentifier -> FormattingOptions -> Session ()
556 formatDoc doc opts = do
557   let params = DocumentFormattingParams doc opts
558   edits <- getResponseResult <$> request TextDocumentFormatting params
559   applyTextEdits doc edits
560
561 -- | Applies formatting to the specified range in a document.
562 formatRange :: TextDocumentIdentifier -> FormattingOptions -> Range -> Session ()
563 formatRange doc opts range = do
564   let params = DocumentRangeFormattingParams doc range opts
565   edits <- getResponseResult <$> request TextDocumentRangeFormatting params
566   applyTextEdits doc edits
567
568 applyTextEdits :: TextDocumentIdentifier -> List TextEdit -> Session ()
569 applyTextEdits doc edits =
570   let wEdit = WorkspaceEdit (Just (HashMap.singleton (doc ^. uri) edits)) Nothing
571       req = RequestMessage "" (IdInt 0) WorkspaceApplyEdit (ApplyWorkspaceEditParams wEdit)
572   in updateState (ReqApplyWorkspaceEdit req)
573
574 -- | Returns the code lenses for the specified document.
575 getCodeLenses :: TextDocumentIdentifier -> Session [CodeLens]
576 getCodeLenses tId = do
577     rsp <- request TextDocumentCodeLens (CodeLensParams tId) :: Session CodeLensResponse
578     case getResponseResult rsp of
579         List res -> pure res