Skip to content

Commit b05ee42

Browse files
committed
Move progress code to its own module
1 parent e47c2e5 commit b05ee42

File tree

4 files changed

+238
-216
lines changed

4 files changed

+238
-216
lines changed

lsp/lsp.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ library
5050
Language.LSP.Server.Control
5151
Language.LSP.Server.Core
5252
Language.LSP.Server.Processing
53+
Language.LSP.Server.Progress
5354

5455
build-depends:
5556
, aeson >=2 && <2.3

lsp/src/Language/LSP/Server.hs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,6 @@ module Language.LSP.Server (
6565
reverseSortEdit,
6666
) where
6767

68+
import Language.LSP.Server.Progress
6869
import Language.LSP.Server.Control
6970
import Language.LSP.Server.Core

lsp/src/Language/LSP/Server/Core.hs

Lines changed: 0 additions & 216 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ import Colog.Core (
1919
WithSeverity (..),
2020
(<&),
2121
)
22-
import Control.Concurrent.Async
2322
import Control.Concurrent.Extra as C
2423
import Control.Concurrent.STM
25-
import Control.Exception qualified as E
2624
import Control.Lens (at, (^.), (^?), _Just)
2725
import Control.Monad
2826
import Control.Monad.Catch (
@@ -38,7 +36,6 @@ import Control.Monad.Trans.Identity
3836
import Control.Monad.Trans.Reader
3937
import Data.Aeson qualified as J
4038
import Data.Default
41-
import Data.Foldable
4239
import Data.Functor.Product
4340
import Data.HashMap.Strict qualified as HM
4441
import Data.IxMap
@@ -65,8 +62,6 @@ import Language.LSP.Protocol.Utils.SMethodMap qualified as SMethodMap
6562
import Language.LSP.VFS hiding (end)
6663
import Prettyprinter
6764
import System.Random hiding (next)
68-
import UnliftIO qualified as U
69-
import UnliftIO.Exception qualified as UE
7065

7166
-- ---------------------------------------------------------------------
7267
{-# ANN module ("HLint: ignore Eta reduce" :: String) #-}
@@ -323,29 +318,6 @@ instance Default Options where
323318
defaultOptions :: Options
324319
defaultOptions = def
325320

326-
{- | A package indicating the percentage of progress complete and a
327-
an optional message to go with it during a 'withProgress'
328-
329-
@since 0.10.0.0
330-
-}
331-
data ProgressAmount = ProgressAmount (Maybe UInt) (Maybe Text)
332-
333-
{- | Thrown if the user cancels a 'Cancellable' 'withProgress'/'withIndefiniteProgress'/ session
334-
335-
@since 0.11.0.0
336-
-}
337-
data ProgressCancelledException = ProgressCancelledException
338-
deriving (Show)
339-
340-
instance E.Exception ProgressCancelledException
341-
342-
{- | Whether or not the user should be able to cancel a 'withProgress'/'withIndefiniteProgress'
343-
session
344-
345-
@since 0.11.0.0
346-
-}
347-
data ProgressCancellable = Cancellable | NotCancellable
348-
349321
-- See Note [LSP configuration] for discussion of the configuration-related fields
350322

351323
{- | Contains all the callbacks to use for initialized the language server.
@@ -645,194 +617,6 @@ unregisterCapability (RegistrationToken m (RegistrationId uuid)) = do
645617
params = L.UnregistrationParams [toUntypedUnregistration unregistration]
646618
void $ sendRequest SMethod_ClientUnregisterCapability params $ \_res -> pure ()
647619

648-
--------------------------------------------------------------------------------
649-
-- PROGRESS
650-
--------------------------------------------------------------------------------
651-
652-
-- Get a new id for the progress session and make a new one
653-
getNewProgressId :: MonadLsp config m => m ProgressToken
654-
getNewProgressId = do
655-
stateState (progressNextId . resProgressData) $ \cur ->
656-
let !next = cur + 1
657-
in (L.ProgressToken $ L.InL cur, next)
658-
{-# INLINE getNewProgressId #-}
659-
660-
withProgressBase ::
661-
forall c m a.
662-
MonadLsp c m =>
663-
Bool ->
664-
Text ->
665-
Maybe ProgressToken ->
666-
ProgressCancellable ->
667-
((ProgressAmount -> m ()) -> m a) ->
668-
m a
669-
withProgressBase indefinite title clientToken cancellable f = do
670-
let initialProgress = ProgressAmount (if indefinite then Nothing else Just 0) Nothing
671-
LanguageContextEnv{resProgressStartDelay = startDelay, resProgressUpdateDelay = updateDelay} <- getLspEnv
672-
673-
tokenVar <- liftIO newEmptyTMVarIO
674-
reportVar <- liftIO $ newTMVarIO initialProgress
675-
endBarrier <- liftIO newEmptyMVar
676-
677-
let
678-
updater :: ProgressAmount -> m ()
679-
updater pa = liftIO $ atomically $ do
680-
-- I don't know of a way to do this with a normal MVar!
681-
-- That is: put something into it regardless of whether it is full or empty
682-
_ <- tryTakeTMVar reportVar
683-
putTMVar reportVar pa
684-
685-
progressEnded :: IO ()
686-
progressEnded = readMVar endBarrier
687-
688-
endProgress :: IO ()
689-
endProgress = void $ tryPutMVar endBarrier ()
690-
691-
-- Once we have a 'ProgressToken', store it in the variable and also register the cancellation
692-
-- handler.
693-
registerToken :: ProgressToken -> m ()
694-
registerToken t = do
695-
handlers <- getProgressCancellationHandlers
696-
liftIO $ atomically $ do
697-
putTMVar tokenVar t
698-
modifyTVar handlers (Map.insert t endProgress)
699-
700-
-- Deregister our 'ProgressToken', specifically its cancellation handler. It is important
701-
-- to do this reliably or else we will leak handlers.
702-
unregisterToken :: m ()
703-
unregisterToken = do
704-
handlers <- getProgressCancellationHandlers
705-
liftIO $ atomically $ do
706-
mt <- tryReadTMVar tokenVar
707-
for_ mt $ \t -> modifyTVar handlers (Map.delete t)
708-
709-
-- Find and register our 'ProgressToken', asking the client for it if necessary.
710-
-- Note that this computation may terminate before we get the token, we need to wait
711-
-- for the token var to be filled if we want to use it.
712-
createToken :: m ()
713-
createToken = do
714-
-- See Note [Delayed progress reporting]
715-
-- This delays the creation of the token as well as the 'begin' message. Creating
716-
-- the token shouldn't result in any visible action on the client side since
717-
-- the title/initial percentage aren't given until the 'begin' mesage. However,
718-
-- it's neater not to create tokens that we won't use, and clients may find it
719-
-- easier to clean them up if they receive begin/end reports for them.
720-
liftIO $ threadDelay startDelay
721-
case clientToken of
722-
-- See Note [Client- versus server-initiated progress]
723-
-- Client-initiated progress
724-
Just t -> registerToken t
725-
-- Try server-initiated progress
726-
Nothing -> do
727-
t <- getNewProgressId
728-
clientCaps <- getClientCapabilities
729-
730-
-- If we don't have a progress token from the client and
731-
-- the client doesn't support server-initiated progress then
732-
-- there's nothing to do: we can't report progress.
733-
when (clientSupportsServerInitiatedProgress clientCaps)
734-
$ void
735-
$
736-
-- Server-initiated progress
737-
-- See Note [Client- versus server-initiated progress]
738-
sendRequest
739-
SMethod_WindowWorkDoneProgressCreate
740-
(WorkDoneProgressCreateParams t)
741-
$ \case
742-
-- Successfully registered the token, we can now use it.
743-
-- So we go ahead and start. We do this as soon as we get the
744-
-- token back so the client gets feedback ASAP
745-
Right _ -> registerToken t
746-
-- The client sent us an error, we can't use the token.
747-
Left _err -> pure ()
748-
749-
-- Actually send the progress reports.
750-
sendReports :: m ()
751-
sendReports = do
752-
t <- liftIO $ atomically $ readTMVar tokenVar
753-
begin t
754-
-- Once we are sending updates, if we get interrupted we should send
755-
-- the end notification
756-
update t `UE.finally` end t
757-
where
758-
cancellable' = case cancellable of
759-
Cancellable -> Just True
760-
NotCancellable -> Just False
761-
begin t = do
762-
(ProgressAmount pct msg) <- liftIO $ atomically $ takeTMVar reportVar
763-
sendProgressReport t $ WorkDoneProgressBegin L.AString title cancellable' msg pct
764-
update t =
765-
forever $ do
766-
-- See Note [Delayed progress reporting]
767-
liftIO $ threadDelay updateDelay
768-
(ProgressAmount pct msg) <- liftIO $ atomically $ takeTMVar reportVar
769-
sendProgressReport t $ WorkDoneProgressReport L.AString Nothing msg pct
770-
end t = sendProgressReport t (WorkDoneProgressEnd L.AString Nothing)
771-
772-
-- Create the token and then start sending reports; all of which races with the check for the
773-
-- progress having ended. In all cases, make sure to unregister the token at the end.
774-
progressThreads :: m ()
775-
progressThreads =
776-
((createToken >> sendReports) `UE.finally` unregisterToken) `U.race_` liftIO progressEnded
777-
778-
withRunInIO $ \runInBase -> do
779-
withAsync (runInBase $ f updater) $ \mainAct ->
780-
-- If the progress gets cancelled then we need to get cancelled too
781-
withAsync (runInBase progressThreads) $ \pthreads -> do
782-
r <- waitEither mainAct pthreads
783-
-- TODO: is this weird? I can't see how else to gracefully use the ending barrier
784-
-- as a guard to cancel the other async
785-
case r of
786-
Left a -> pure a
787-
Right _ -> cancelWith mainAct ProgressCancelledException >> wait mainAct
788-
where
789-
sendProgressReport :: (J.ToJSON r) => ProgressToken -> r -> m ()
790-
sendProgressReport token report = sendNotification SMethod_Progress $ ProgressParams token $ J.toJSON report
791-
792-
getProgressCancellationHandlers :: m (TVar (Map.Map ProgressToken (IO ())))
793-
getProgressCancellationHandlers = getStateVar (progressCancel . resProgressData)
794-
795-
clientSupportsServerInitiatedProgress :: L.ClientCapabilities -> Bool
796-
clientSupportsServerInitiatedProgress caps = fromMaybe False $ caps ^? L.window . _Just . L.workDoneProgress . _Just
797-
{-# INLINE clientSupportsServerInitiatedProgress #-}
798-
799-
{- |
800-
Wrapper for reporting progress to the client during a long running task.
801-
-}
802-
withProgress ::
803-
MonadLsp c m =>
804-
-- | The title of the progress operation
805-
Text ->
806-
-- | The progress token provided by the client in the method params, if any
807-
Maybe ProgressToken ->
808-
-- | Whether or not this operation is cancellable. If true, the user will be
809-
-- shown a button to allow cancellation. Note that requests can still be cancelled
810-
-- even if this is not set.
811-
ProgressCancellable ->
812-
-- | An update function to pass progress updates to
813-
((ProgressAmount -> m ()) -> m a) ->
814-
m a
815-
withProgress title clientToken cancellable f = withProgressBase False title clientToken cancellable f
816-
817-
{- |
818-
Same as 'withProgress', but for processes that do not report the precentage complete.
819-
-}
820-
withIndefiniteProgress ::
821-
MonadLsp c m =>
822-
-- | The title of the progress operation
823-
Text ->
824-
-- | The progress token provided by the client in the method params, if any
825-
Maybe ProgressToken ->
826-
-- | Whether or not this operation is cancellable. If true, the user will be
827-
-- shown a button to allow cancellation. Note that requests can still be cancelled
828-
-- even if this is not set.
829-
ProgressCancellable ->
830-
-- | An update function to pass progress updates to
831-
((Text -> m ()) -> m a) ->
832-
m a
833-
withIndefiniteProgress title clientToken cancellable f =
834-
withProgressBase True title clientToken cancellable (\update -> f (\msg -> update (ProgressAmount Nothing (Just msg))))
835-
836620
-- ---------------------------------------------------------------------
837621

838622
{- | Aggregate all diagnostics pertaining to a particular version of a document,

0 commit comments

Comments
 (0)