1- {-# LANGUAGE ApplicativeDo #-}
1+ {-# LANGUAGE ApplicativeDo, RecursiveDo #-}
22module Glean.LSP (main ) where
33
44import Control.Monad
55import Control.Monad.Catch
6+ import Control.Monad.Fix
67import Control.Monad.IO.Class
78import Control.Monad.IO.Unlift
89import Control.Monad.Trans.Class
@@ -23,6 +24,7 @@ import qualified Language.LSP.Protocol.Message as LSP
2324import qualified Language.LSP.Protocol.Types as LSP
2425import Options.Applicative
2526import Thrift.Protocol
27+ import UnliftIO.Async
2628import UnliftIO.Exception
2729import UnliftIO.IORef
2830import Util.Log.Text
@@ -37,10 +39,10 @@ import qualified Glean.Glass.Types as Glass
3739import qualified Glean.Glass.Handler.Documents as Glass.Handler
3840import qualified Glean.Glass.Handler.Symbols as Glass.Handler
3941
42+ import Data.ConcurrentCache as ConcurrentCache
4043import Data.Path as Path
4144
4245{- TODO / ideas
43- - concurrent requests and cancellation
4446 - go to decl / go to impl / go to type def?
4547 - documentSymbols:
4648 - make hierarchical
@@ -103,14 +105,13 @@ data LspEnv = LspEnv {
103105 options :: LspOptions ,
104106 wsRoot :: AbsPath ,
105107 glass :: Glass. Env ,
106- -- TODO: use a better symbol cache so that if two threads simultaneously
107- -- try to fetch symbols for a file we should only make a single Glass request.
108- symbolCache :: IORef (HashMap RelPath Glass. DocumentSymbolIndex )
108+ symbolCache :: ConcurrentCache RelPath Glass. DocumentSymbolIndex ,
109+ requests :: IORef (HashMap LSP. SomeLspId (Async () ))
109110 }
110111
111112newtype GleanLspM a =
112113 GleanLspM { unGleanLspM :: ReaderT (IORef (Maybe LspEnv )) (LspM LspConfig ) a }
113- deriving (Monad , Applicative , Functor , MonadIO , MonadThrow , MonadUnliftIO )
114+ deriving (Monad , Applicative , Functor , MonadIO , MonadThrow , MonadUnliftIO , MonadFix )
114115
115116deriving instance LSP. MonadLsp LspConfig GleanLspM
116117
@@ -130,6 +131,77 @@ getGleanLspEnv = GleanLspM $ do
130131 Nothing -> throwIO $ GleanLspException " not initialized"
131132 Just env -> return env
132133
134+ -- -----------------------------------------------------------------------------
135+ -- Executing requests
136+
137+ -- | Perform a request asynchronously. It can subsequently be
138+ -- cancelled by 'cancelRequest', which will send the appropriate
139+ -- @RequestCancelled@ response back to the client if the request was
140+ -- still in progress at the time of cancellation.
141+ asyncRequest ::
142+ forall (m :: LSP. Method LSP. ClientToServer LSP. Request ) .
143+ (LSP. TRequestMessage m ->
144+ GleanLspM (Either (LSP. TResponseError m ) (LSP. MessageResult m ))) ->
145+ LSP. Handler GleanLspM m
146+ asyncRequest act = \ msg respond -> mdo
147+ env <- getGleanLspEnv
148+ m <- readIORef env. requests
149+ liftIO $ logInfo $ " asyncRequest: " <>
150+ Text. pack (show (HashMap. size m)) <> " in progress"
151+ let
152+ register =
153+ atomicModifyIORef env. requests $ -- not strict due to mdo
154+ \ m -> (HashMap. insert (LSP. SomeLspId msg. _id) async m, () )
155+ unregister =
156+ atomicModifyIORef' env. requests $
157+ \ m -> (HashMap. delete (LSP. SomeLspId msg. _id) m, () )
158+ register
159+ async <- UnliftIO.Exception. mask_ $ asyncWithUnmask $ \ unmask -> do
160+ r <- UnliftIO.Exception. trySyncOrAsync $ unmask $ act msg
161+ -- we want to catch everything, including async cancellation
162+ unregister
163+ case r of
164+ Left (ex :: SomeException )
165+ | Just AsyncCancelled <- fromException ex ->
166+ respond $ Left $ responseCancelled " cancelled by client"
167+ | otherwise -> respond $ Left $ responseException ex
168+ Right result -> respond result
169+ return ()
170+
171+ -- | Cancel a request by LspId
172+ cancelRequest ::
173+ forall (m :: LSP. Method LSP. ClientToServer LSP. Request ) .
174+ LSP. LspId m ->
175+ GleanLspM ()
176+ cancelRequest id = do
177+ env <- getGleanLspEnv
178+ m <- readIORef env. requests
179+ case HashMap. lookup (LSP. SomeLspId id ) m of
180+ Nothing -> logWarning $ " cancelRequest: not found"
181+ Just async -> cancel async
182+
183+ responseCancelled ::
184+ forall (m :: LSP. Method LSP. ClientToServer LSP. Request ) .
185+ Text ->
186+ LSP. TResponseError m
187+ responseCancelled msg =
188+ LSP. TResponseError {
189+ _code = LSP. InL LSP. LSPErrorCodes_RequestCancelled ,
190+ _message = msg,
191+ _xdata = Nothing
192+ }
193+
194+ responseException ::
195+ forall (m :: LSP. Method LSP. ClientToServer LSP. Request ) .
196+ SomeException ->
197+ LSP. TResponseError m
198+ responseException ex =
199+ LSP. TResponseError {
200+ _code = LSP. InL LSP. LSPErrorCodes_RequestFailed ,
201+ _message = Text. pack (show ex),
202+ _xdata = Nothing
203+ }
204+
133205-- -----------------------------------------------------------------------------
134206-- Setup & initialisation
135207
@@ -144,8 +216,9 @@ initServer glass options envRef serverConfig _msg = do
144216 runExceptT $ do
145217 wsRoot <- ExceptT $ LSP. runLspT serverConfig getWsRoot
146218 wsRoot <- filePathToAbs wsRoot
147- symbolCache <- newIORef HashMap. empty
148- writeIORef envRef (Just LspEnv { options, glass, wsRoot, symbolCache })
219+ symbolCache <- ConcurrentCache. new
220+ requests <- newIORef HashMap. empty
221+ writeIORef envRef (Just LspEnv { options, glass, wsRoot, symbolCache, requests })
149222 liftIO $ logInfo $ " wsRoot: " <> Text. pack (Path. toFilePath wsRoot)
150223 pure serverConfig
151224 where
@@ -201,7 +274,7 @@ serverDef glass options = do
201274 , handleReferencesRequest
202275 -- , handleRenameRequest
203276 -- , handlePrepareRenameRequest
204- -- , handleCancelNotification
277+ , handleCancelNotification
205278 , handleDidOpen
206279 -- , handleDidChange
207280 -- , handleDidSave
@@ -259,6 +332,15 @@ handleInitialized =
259332 LSP. notificationHandler LSP. SMethod_Initialized $
260333 pure $ pure ()
261334
335+ handleCancelNotification :: LSP. Handlers GleanLspM
336+ handleCancelNotification =
337+ LSP. notificationHandler LSP. SMethod_CancelRequest $ \ req -> do
338+ let id = case req. _params. _id of
339+ LSP. InL i -> LSP. IdInt i
340+ LSP. InR t -> LSP. IdString t
341+ liftIO $ logInfo $ " cancel: " <> Text. pack (show id )
342+ cancelRequest id
343+
262344handleDidOpen :: LSP. Handlers GleanLspM
263345handleDidOpen =
264346 LSP. notificationHandler LSP. SMethod_TextDocumentDidOpen $ \ message -> do
@@ -274,52 +356,53 @@ handleDidClose =
274356
275357handleDocumentSymbols :: LSP. Handlers GleanLspM
276358handleDocumentSymbols =
277- LSP. requestHandler LSP. SMethod_TextDocumentDocumentSymbol $ \ req res ->
359+ LSP. requestHandler LSP. SMethod_TextDocumentDocumentSymbol $ asyncRequest $ \ req ->
278360 logTimed (" documentSymbols: " <> req. _params. _textDocument. _uri. getUri) $ do
279361 let params = req. _params
280362 path <- uriToAbsPath params. _textDocument. _uri
281363 syms <- getDocumentSymbols path
282364 liftIO $ logInfo $ " symbols: " <> Text. pack (show (length syms))
283- res $ Right $ LSP. InR $ LSP. InL syms
365+ return $ Right $ LSP. InR $ LSP. InL syms
284366
285367handleDefinitionRequest :: LSP. Handlers GleanLspM
286368handleDefinitionRequest =
287- LSP. requestHandler LSP. SMethod_TextDocumentDefinition $ \ req resp -> do
369+ LSP. requestHandler LSP. SMethod_TextDocumentDefinition $ asyncRequest $ \ req -> do
288370 let params = req. _params
289371 logTimed (" definition: " <> params. _textDocument. _uri. getUri <>
290372 Text. pack (show req. _params. _position)) $ do
291373 path <- uriToAbsPath params. _textDocument. _uri
292374 defs <- getDefinition path params. _position
293- resp $ Right . LSP. InR $ LSP. InL defs
375+ return $ Right . LSP. InR $ LSP. InL defs
294376
295377handleSetTrace :: LSP. Handlers GleanLspM
296378handleSetTrace = LSP. notificationHandler LSP. SMethod_SetTrace $ \ _ -> pure ()
297379
298380handleTextDocumentHoverRequest :: LSP. Handlers GleanLspM
299381handleTextDocumentHoverRequest =
300- LSP. requestHandler LSP. SMethod_TextDocumentHover $ \ req resp -> do
382+ LSP. requestHandler LSP. SMethod_TextDocumentHover $ asyncRequest $ \ req -> do
301383 let hoverParams = req. _params
302384 logTimed (" hover: " <> hoverParams. _textDocument. _uri. getUri <>
303385 Text. pack (show hoverParams. _position)) $ do
304386 path <- uriToAbsPath hoverParams. _textDocument. _uri
305387 hover <- retrieveHover path hoverParams. _position
306- resp $ Right $ LSP. maybeToNull hover
388+ return $ Right $ LSP. maybeToNull hover
307389
308390handleReferencesRequest :: LSP. Handlers GleanLspM
309391handleReferencesRequest =
310- LSP. requestHandler LSP. SMethod_TextDocumentReferences $ \ req res -> do
392+ LSP. requestHandler LSP. SMethod_TextDocumentReferences $ asyncRequest $ \ req -> do
311393 let params = req. _params
312394 logTimed (" references: " <> params. _textDocument. _uri. getUri <>
313395 Text. pack (show params. _position)) $ do
314396 path <- uriToAbsPath params. _textDocument. _uri
315397 refs <- findRefs path params. _position
316- res $ Right $ LSP. InL refs
398+ return $ Right $ LSP. InL refs
317399
318400handleWorkspaceSymbol :: LSP. Handlers GleanLspM
319- handleWorkspaceSymbol = LSP. requestHandler LSP. SMethod_WorkspaceSymbol $ \ req res -> do
320- -- https://hackage.haskell.org/package/lsp-types-1.6.0.0/docs/Language-LSP-Types.html#t:WorkspaceSymbolParams
321- symbols <- symbolSearch req. _params. _query
322- res $ Right . LSP. InL $ symbols
401+ handleWorkspaceSymbol =
402+ LSP. requestHandler LSP. SMethod_WorkspaceSymbol $ asyncRequest $ \ req ->
403+ logTimed (" search: " <> req. _params. _query) $ do
404+ symbols <- symbolSearch req. _params. _query
405+ return $ Right . LSP. InL $ symbols
323406
324407-- -----------------------------------------------------------------------------
325408-- Glean / Glass stuff
@@ -354,27 +437,20 @@ getSymbols path includeRefs = do
354437getSymbolsCached :: AbsPath -> GleanLspM Glass. DocumentSymbolIndex
355438getSymbolsCached path = do
356439 env <- getGleanLspEnv
357- cache <- readIORef env. symbolCache
358440 let relPath = Path. makeRelative env. wsRoot path
359- case HashMap. lookup relPath cache of
360- Just symbols -> return symbols
361- Nothing -> do
362- symbols <- getSymbols relPath True {- includeRefs -}
363- atomicModifyIORef' env. symbolCache $ \ old ->
364- (HashMap. insert relPath symbols old, () )
365- return symbols
441+ ConcurrentCache. insert relPath env. symbolCache $
442+ getSymbols relPath True {- includeRefs -}
366443
367444flushSymbolCache :: GleanLspM ()
368445flushSymbolCache = do
369446 env <- getGleanLspEnv
370- writeIORef env. symbolCache HashMap. empty
447+ ConcurrentCache. flush env. symbolCache
371448
372449removeCachedSymbols :: AbsPath -> GleanLspM ()
373450removeCachedSymbols path = do
374451 env <- getGleanLspEnv
375452 let relPath = Path. makeRelative env. wsRoot path
376- atomicModifyIORef' env. symbolCache $ \ cache ->
377- (HashMap. delete relPath cache, () )
453+ ConcurrentCache. remove relPath env. symbolCache
378454
379455findSymbol ::
380456 LSP. Position ->
@@ -588,3 +664,4 @@ logTimed msg io = do
588664 liftIO $ logInfo $ msg <> " : " <>
589665 Text. pack (showTime t) <> " , " <> Text. pack (showAllocs b)
590666 return a
667+
0 commit comments