66{-# LANGUAGE OverloadedStrings #-}
77{-# LANGUAGE RoleAnnotations #-}
88{-# LANGUAGE TypeFamilyDependencies #-}
9- {-# LANGUAGE NumericUnderscores #-}
109{-# LANGUAGE UndecidableInstances #-}
1110{-# LANGUAGE CUSKs #-}
1211{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
@@ -66,6 +65,7 @@ import Language.LSP.Protocol.Utils.SMethodMap qualified as SMethodMap
6665import Language.LSP.VFS hiding (end )
6766import Prettyprinter
6867import System.Random hiding (next )
68+ import UnliftIO qualified as U
6969import UnliftIO.Exception qualified as UE
7070
7171-- ---------------------------------------------------------------------
@@ -244,21 +244,25 @@ data VFSData = VFSData
244244{-# INLINE modifyState #-}
245245modifyState :: MonadLsp config m => (LanguageContextState config -> TVar a ) -> (a -> a ) -> m ()
246246modifyState sel f = do
247- tvarDat <- sel . resState <$> getLspEnv
247+ tvarDat <- getStateVar sel
248248 liftIO $ atomically $ modifyTVar' tvarDat f
249249
250250{-# INLINE stateState #-}
251251stateState :: MonadLsp config m => (LanguageContextState config -> TVar s ) -> (s -> (a , s )) -> m a
252252stateState sel f = do
253- tvarDat <- sel . resState <$> getLspEnv
253+ tvarDat <- getStateVar sel
254254 liftIO $ atomically $ stateTVar tvarDat f
255255
256256{-# INLINE getsState #-}
257257getsState :: MonadLsp config m => (LanguageContextState config -> TVar a ) -> m a
258258getsState f = do
259- tvarDat <- f . resState <$> getLspEnv
259+ tvarDat <- getStateVar f
260260 liftIO $ readTVarIO tvarDat
261261
262+ {-# INLINE getStateVar #-}
263+ getStateVar :: MonadLsp config m => (LanguageContextState config -> TVar a ) -> m (TVar a )
264+ getStateVar f = f . resState <$> getLspEnv
265+
262266-- ---------------------------------------------------------------------
263267
264268{- | Options that the server may configure.
@@ -313,8 +317,8 @@ instance Default Options where
313317 Nothing
314318 False
315319 -- See Note [Delayed progress reporting]
316- 1_000_000
317- 5_00_000
320+ 0
321+ 0
318322
319323defaultOptions :: Options
320324defaultOptions = def
@@ -645,14 +649,6 @@ unregisterCapability (RegistrationToken m (RegistrationId uuid)) = do
645649-- PROGRESS
646650--------------------------------------------------------------------------------
647651
648- addProgressCancellationHandler :: MonadLsp config m => ProgressToken -> IO () -> m ()
649- addProgressCancellationHandler n act = modifyState (progressCancel . resProgressData) $ Map. insert n act
650- {-# INLINE addProgressCancellationHandler #-}
651-
652- deleteProgressCancellationHandler :: MonadLsp config m => ProgressToken -> m ()
653- deleteProgressCancellationHandler n = modifyState (progressCancel . resProgressData) $ Map. delete n
654- {-# INLINE deleteProgressCancellationHandler #-}
655-
656652-- Get a new id for the progress session and make a new one
657653getNewProgressId :: MonadLsp config m => m ProgressToken
658654getNewProgressId = do
@@ -673,44 +669,56 @@ data ProgressTracker = ProgressTracker
673669 -- set it when it finishes the work.
674670 }
675671
676- -- | Create a 'ProgressTracker'.
677- makeProgressTracker ::
678- forall c m .
672+ withProgressBase ::
673+ forall c m a .
679674 MonadLsp c m =>
675+ Bool ->
680676 Text ->
681- ProgressAmount ->
682677 Maybe ProgressToken ->
683678 ProgressCancellable ->
684- m ProgressTracker
685- makeProgressTracker title initialProgress clientToken cancellable = do
679+ ((ProgressAmount -> m () ) -> m a ) ->
680+ m a
681+ withProgressBase indefinite title clientToken cancellable f = do
682+ let initialProgress = ProgressAmount (if indefinite then Nothing else Just 0 ) Nothing
686683 LanguageContextEnv {resProgressStartDelay = startDelay, resProgressUpdateDelay = updateDelay} <- getLspEnv
687684
688685 tokenVar <- liftIO newEmptyTMVarIO
689686 reportVar <- liftIO $ newTMVarIO initialProgress
690687 endBarrier <- liftIO newEmptyMVar
691688
692689 let
693- sendProgressReport :: (J. ToJSON r ) => ProgressToken -> r -> m ()
694- sendProgressReport token report = sendNotification SMethod_Progress $ ProgressParams token $ J. toJSON report
690+ updater :: ProgressAmount -> m ()
691+ updater pa = liftIO $ atomically $ do
692+ -- I don't know of a way to do this with a normal MVar!
693+ -- That is: put something into it regardless of whether it is full or empty
694+ _ <- tryTakeTMVar reportVar
695+ putTMVar reportVar pa
696+
697+ progressEnded :: IO ()
698+ progressEnded = readMVar endBarrier
695699
696- -- \| Once we have a 'ProgressToken', store it in the variable and also register the cancellation
700+ endProgress :: IO ()
701+ endProgress = void $ tryPutMVar endBarrier ()
702+
703+ -- Once we have a 'ProgressToken', store it in the variable and also register the cancellation
697704 -- handler.
698705 registerToken :: ProgressToken -> m ()
699706 registerToken t = do
700- -- TODO: this is currently racy, we need these two to occur in one STM
701- -- transaction
702- liftIO $ atomically $ putTMVar tokenVar t
703- addProgressCancellationHandler t (void $ tryPutMVar endBarrier () )
707+ handlers <- getProgressCancellationHandlers
708+ liftIO $ atomically $ do
709+ putTMVar tokenVar t
710+ modifyTVar handlers ( Map. insert t endProgress )
704711
705- -- \| Deregister our 'ProgressToken', specifically its cancellation handler. It is important
712+ -- Deregister our 'ProgressToken', specifically its cancellation handler. It is important
706713 -- to do this reliably or else we will leak handlers.
707714 unregisterToken :: m ()
708715 unregisterToken = do
709- -- TODO: this is also racy, see above
710- t <- liftIO $ atomically $ tryReadTMVar tokenVar
711- for_ t deleteProgressCancellationHandler
716+ handlers <- getProgressCancellationHandlers
717+ liftIO $ atomically $ do
718+ mt <- tryReadTMVar tokenVar
719+ for_ mt $ \ t -> modifyTVar handlers (Map. delete t)
712720
713- -- \| Find and register our 'ProgressToken', asking the client for it if necessary.
721+ -- Find and register our 'ProgressToken', asking the client for it if necessary.
714722 -- Note that this computation may terminate before we get the token, we need to wait
715723 -- for the token var to be filled if we want to use it.
716724 createToken :: m ()
@@ -743,7 +751,7 @@ makeProgressTracker title initialProgress clientToken cancellable = do
743751 -- The client sent us an error, we can't use the token.
744752 Left _err -> pure ()
745753
746- -- \| Actually send the progress reports.
754+ -- Actually send the progress reports.
747755 sendReports :: m ()
748756 sendReports = do
749757 t <- liftIO $ atomically $ readTMVar tokenVar
@@ -771,54 +779,28 @@ makeProgressTracker title initialProgress clientToken cancellable = do
771779 sendProgressReport t $ WorkDoneProgressReport L. AString Nothing msg pct
772780 end t = sendProgressReport t (WorkDoneProgressEnd L. AString Nothing )
773781
774- -- \| Blocks until the progress reporting should end.
775- endProgress :: IO ()
776- endProgress = readMVar endBarrier
777-
778- progressThreads :: m (Async () )
779- progressThreads = withRunInIO $ \ runInBase ->
780- async $
781- -- Create the token and then start sending reports; all of which races with the check for the
782- -- progress having ended. In all cases, make sure to unregister the token at the end.
783- (runInBase (createToken >> sendReports) `race_` endProgress) `E.finally` runInBase unregisterToken
784-
785- -- Launch the threads with no handle, rely on the end barrier to kill them
786- _threads <- progressThreads
787-
788- -- The update function for clients: just write to the var
789- let update pa = atomically $ do
790- -- I don't know of a way to do this with a normal MVar!
791- -- That is: put something into it regardless of whether it is full or empty
792- _ <- tryTakeTMVar reportVar
793- putTMVar reportVar pa
794- pure $ ProgressTracker update endBarrier
782+ -- Create the token and then start sending reports; all of which races with the check for the
783+ -- progress having ended. In all cases, make sure to unregister the token at the end.
784+ progressThreads :: m ()
785+ progressThreads =
786+ ((createToken >> sendReports) `UE.finally` unregisterToken) `U.race_` liftIO progressEnded
787+
788+ withRunInIO $ \ runInBase -> do
789+ withAsync (runInBase $ f updater) $ \ mainAct ->
790+ -- If the progress gets cancelled then we need to get cancelled too
791+ withAsync (runInBase progressThreads) $ \ pthreads -> do
792+ r <- waitEither mainAct pthreads
793+ -- TODO: is this weird? I can't see how else to gracefully use the ending barrier
794+ -- as a guard to cancel the other async
795+ case r of
796+ Left a -> pure a
797+ Right _ -> cancelWith mainAct ProgressCancelledException >> wait mainAct
798+ where
799+ sendProgressReport :: (J. ToJSON r ) => ProgressToken -> r -> m ()
800+ sendProgressReport token report = sendNotification SMethod_Progress $ ProgressParams token $ J. toJSON report
795801
796- withProgressBase ::
797- forall c m a .
798- MonadLsp c m =>
799- Bool ->
800- Text ->
801- Maybe ProgressToken ->
802- ProgressCancellable ->
803- ((ProgressAmount -> m () ) -> m a ) ->
804- m a
805- withProgressBase indefinite title clientToken cancellable f = withRunInIO $ \ runInBase -> do
806- let initialPercentage = if indefinite then Nothing else Just 0
807- E. bracket
808- -- Create the progress tracker, which will start the progress threads
809- (runInBase $ makeProgressTracker title (ProgressAmount initialPercentage Nothing ) clientToken cancellable)
810- -- When we finish, trigger the progress ending barrier
811- (\ tracker -> tryPutMVar (progressEnded tracker) () )
812- $ \ tracker -> do
813- -- Tie the given computation to the progress ending barrier so it will cancel us if triggered
814- withAsync (runInBase $ f (liftIO . updateProgress tracker)) $ \ mainAct ->
815- withAsync (readMVar (progressEnded tracker)) $ \ ender -> do
816- -- TODO: is this weird? I can't see how else to gracefully use the ending barrier
817- -- as a guard to cancel the other async
818- r <- waitEither mainAct ender
819- case r of
820- Left a -> pure a
821- Right _ -> cancelWith mainAct ProgressCancelledException >> wait mainAct
802+ getProgressCancellationHandlers :: m (TVar (Map. Map ProgressToken (IO () )))
803+ getProgressCancellationHandlers = getStateVar (progressCancel . resProgressData)
822804
823805clientSupportsServerInitiatedProgress :: L. ClientCapabilities -> Bool
824806clientSupportsServerInitiatedProgress caps = fromMaybe False $ caps ^? L. window . _Just . L. workDoneProgress . _Just
@@ -1050,13 +1032,7 @@ like the client's job. Nonetheless, this does not always happen, and so it is he
10501032to moderate the spam.
10511033
10521034For this reason we have configurable delays on starting progress tracking and on sending
1053- updates.
1054-
1055- The default values we use are based on the usual interface responsiveness research:
1056- - 1s is about the point at which people definitely notice something is happening, so
1057- this is where we start progress reporting.
1058- - Updates are at 0.5s, so they happen fast enough that things are clearly happening,
1059- without being too distracting.
1035+ updates. However, the defaults are set to 0, so it's opt-in.
10601036-}
10611037
10621038{- Note [Request cancellation]
0 commit comments