6
6
{-# LANGUAGE OverloadedStrings #-}
7
7
{-# LANGUAGE RoleAnnotations #-}
8
8
{-# LANGUAGE TypeFamilyDependencies #-}
9
- {-# LANGUAGE NumericUnderscores #-}
10
9
{-# LANGUAGE UndecidableInstances #-}
11
10
{-# LANGUAGE CUSKs #-}
12
11
{-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-}
@@ -66,6 +65,7 @@ import Language.LSP.Protocol.Utils.SMethodMap qualified as SMethodMap
66
65
import Language.LSP.VFS hiding (end )
67
66
import Prettyprinter
68
67
import System.Random hiding (next )
68
+ import UnliftIO qualified as U
69
69
import UnliftIO.Exception qualified as UE
70
70
71
71
-- ---------------------------------------------------------------------
@@ -244,21 +244,25 @@ data VFSData = VFSData
244
244
{-# INLINE modifyState #-}
245
245
modifyState :: MonadLsp config m => (LanguageContextState config -> TVar a ) -> (a -> a ) -> m ()
246
246
modifyState sel f = do
247
- tvarDat <- sel . resState <$> getLspEnv
247
+ tvarDat <- getStateVar sel
248
248
liftIO $ atomically $ modifyTVar' tvarDat f
249
249
250
250
{-# INLINE stateState #-}
251
251
stateState :: MonadLsp config m => (LanguageContextState config -> TVar s ) -> (s -> (a , s )) -> m a
252
252
stateState sel f = do
253
- tvarDat <- sel . resState <$> getLspEnv
253
+ tvarDat <- getStateVar sel
254
254
liftIO $ atomically $ stateTVar tvarDat f
255
255
256
256
{-# INLINE getsState #-}
257
257
getsState :: MonadLsp config m => (LanguageContextState config -> TVar a ) -> m a
258
258
getsState f = do
259
- tvarDat <- f . resState <$> getLspEnv
259
+ tvarDat <- getStateVar f
260
260
liftIO $ readTVarIO tvarDat
261
261
262
+ {-# INLINE getStateVar #-}
263
+ getStateVar :: MonadLsp config m => (LanguageContextState config -> TVar a ) -> m (TVar a )
264
+ getStateVar f = f . resState <$> getLspEnv
265
+
262
266
-- ---------------------------------------------------------------------
263
267
264
268
{- | Options that the server may configure.
@@ -313,8 +317,8 @@ instance Default Options where
313
317
Nothing
314
318
False
315
319
-- See Note [Delayed progress reporting]
316
- 1_000_000
317
- 5_00_000
320
+ 0
321
+ 0
318
322
319
323
defaultOptions :: Options
320
324
defaultOptions = def
@@ -645,14 +649,6 @@ unregisterCapability (RegistrationToken m (RegistrationId uuid)) = do
645
649
-- PROGRESS
646
650
--------------------------------------------------------------------------------
647
651
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
-
656
652
-- Get a new id for the progress session and make a new one
657
653
getNewProgressId :: MonadLsp config m => m ProgressToken
658
654
getNewProgressId = do
@@ -673,44 +669,56 @@ data ProgressTracker = ProgressTracker
673
669
-- set it when it finishes the work.
674
670
}
675
671
676
- -- | Create a 'ProgressTracker'.
677
- makeProgressTracker ::
678
- forall c m .
672
+ withProgressBase ::
673
+ forall c m a .
679
674
MonadLsp c m =>
675
+ Bool ->
680
676
Text ->
681
- ProgressAmount ->
682
677
Maybe ProgressToken ->
683
678
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
686
683
LanguageContextEnv {resProgressStartDelay = startDelay, resProgressUpdateDelay = updateDelay} <- getLspEnv
687
684
688
685
tokenVar <- liftIO newEmptyTMVarIO
689
686
reportVar <- liftIO $ newTMVarIO initialProgress
690
687
endBarrier <- liftIO newEmptyMVar
691
688
692
689
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
695
699
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
697
704
-- handler.
698
705
registerToken :: ProgressToken -> m ()
699
706
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 )
704
711
705
- -- \| Deregister our 'ProgressToken', specifically its cancellation handler. It is important
712
+ -- Deregister our 'ProgressToken', specifically its cancellation handler. It is important
706
713
-- to do this reliably or else we will leak handlers.
707
714
unregisterToken :: m ()
708
715
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)
712
720
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.
714
722
-- Note that this computation may terminate before we get the token, we need to wait
715
723
-- for the token var to be filled if we want to use it.
716
724
createToken :: m ()
@@ -743,7 +751,7 @@ makeProgressTracker title initialProgress clientToken cancellable = do
743
751
-- The client sent us an error, we can't use the token.
744
752
Left _err -> pure ()
745
753
746
- -- \| Actually send the progress reports.
754
+ -- Actually send the progress reports.
747
755
sendReports :: m ()
748
756
sendReports = do
749
757
t <- liftIO $ atomically $ readTMVar tokenVar
@@ -771,54 +779,28 @@ makeProgressTracker title initialProgress clientToken cancellable = do
771
779
sendProgressReport t $ WorkDoneProgressReport L. AString Nothing msg pct
772
780
end t = sendProgressReport t (WorkDoneProgressEnd L. AString Nothing )
773
781
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
795
801
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)
822
804
823
805
clientSupportsServerInitiatedProgress :: L. ClientCapabilities -> Bool
824
806
clientSupportsServerInitiatedProgress 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
1050
1032
to moderate the spam.
1051
1033
1052
1034
For 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.
1060
1036
-}
1061
1037
1062
1038
{- Note [Request cancellation]
0 commit comments