@@ -17,16 +17,15 @@ import Control.Monad
17
17
import Control.Monad.IO.Class
18
18
import Control.Monad.IO.Unlift
19
19
import Data.Aeson qualified as J
20
- import Data.Foldable
21
20
import Data.Map.Strict qualified as Map
22
21
import Data.Maybe
23
22
import Data.Text (Text )
23
+ import Data.Text qualified as T
24
24
import Language.LSP.Protocol.Lens qualified as L
25
25
import Language.LSP.Protocol.Message
26
26
import Language.LSP.Protocol.Types
27
27
import Language.LSP.Protocol.Types qualified as L
28
28
import Language.LSP.Server.Core
29
- import UnliftIO qualified as U
30
29
import UnliftIO.Exception qualified as UE
31
30
32
31
{- | A package indicating the percentage of progress complete and a
@@ -53,16 +52,16 @@ instance E.Exception ProgressCancelledException
53
52
data ProgressCancellable = Cancellable | NotCancellable
54
53
55
54
-- Get a new id for the progress session and make a new one
56
- getNewProgressId :: MonadLsp config m = > m ProgressToken
57
- getNewProgressId = do
55
+ getNewProgressId :: ( MonadLsp config m ) => Text - > m ProgressToken
56
+ getNewProgressId title = do
58
57
stateState (progressNextId . resProgressData) $ \ cur ->
59
58
let ! next = cur + 1
60
- in (L. ProgressToken $ L. InL cur, next)
59
+ in (L. ProgressToken $ L. InR (title <> T. pack ( show cur)) , next)
61
60
{-# INLINE getNewProgressId #-}
62
61
63
62
withProgressBase ::
64
63
forall c m a .
65
- MonadLsp c m =>
64
+ ( MonadLsp c m ) =>
66
65
Bool ->
67
66
Text ->
68
67
Maybe ProgressToken ->
@@ -102,12 +101,10 @@ withProgressBase indefinite title clientToken cancellable f = do
102
101
103
102
-- Deregister our 'ProgressToken', specifically its cancellation handler. It is important
104
103
-- to do this reliably or else we will leak handlers.
105
- unregisterToken :: m ()
106
- unregisterToken = do
104
+ unregisterToken :: ProgressToken -> m ()
105
+ unregisterToken token = do
107
106
handlers <- getProgressCancellationHandlers
108
- liftIO $ atomically $ do
109
- mt <- tryReadTMVar tokenVar
110
- for_ mt $ \ t -> modifyTVar handlers (Map. delete t)
107
+ liftIO $ atomically $ modifyTVar handlers (Map. delete token)
111
108
112
109
-- Find and register our 'ProgressToken', asking the client for it if necessary.
113
110
-- Note that this computation may terminate before we get the token, we need to wait
@@ -120,14 +117,14 @@ withProgressBase indefinite title clientToken cancellable f = do
120
117
-- the title/initial percentage aren't given until the 'begin' mesage. However,
121
118
-- it's neater not to create tokens that we won't use, and clients may find it
122
119
-- easier to clean them up if they receive begin/end reports for them.
123
- liftIO $ threadDelay startDelay
120
+ when (startDelay > 0 ) $ liftIO $ threadDelay startDelay
124
121
case clientToken of
125
122
-- See Note [Client- versus server-initiated progress]
126
123
-- Client-initiated progress
127
124
Just t -> registerToken t
128
125
-- Try server-initiated progress
129
126
Nothing -> do
130
- t <- getNewProgressId
127
+ t <- getNewProgressId title
131
128
clientCaps <- getClientCapabilities
132
129
133
130
-- If we don't have a progress token from the client and
@@ -145,43 +142,54 @@ withProgressBase indefinite title clientToken cancellable f = do
145
142
-- Successfully registered the token, we can now use it.
146
143
-- So we go ahead and start. We do this as soon as we get the
147
144
-- token back so the client gets feedback ASAP
148
- Right _ -> registerToken t
145
+ Right _ -> do
146
+ registerToken t
149
147
-- The client sent us an error, we can't use the token.
150
- Left _err -> pure ()
151
-
152
- -- Actually send the progress reports.
153
- sendReports :: m ()
154
- sendReports = do
155
- t <- liftIO $ atomically $ readTMVar tokenVar
156
- begin t
157
- -- Once we are sending updates, if we get interrupted we should send
158
- -- the end notification
159
- update t `UE.finally` end t
160
- where
161
- cancellable' = case cancellable of
162
- Cancellable -> Just True
163
- NotCancellable -> Just False
164
- begin t = do
148
+ Left _err -> do
149
+ pure ()
150
+
151
+ update t = do
152
+ forever $ do
153
+ -- See Note [Delayed progress reporting]
154
+ when (updateDelay > 0 ) $ liftIO $ threadDelay updateDelay
165
155
(ProgressAmount pct msg) <- liftIO $ atomically $ takeTMVar reportVar
166
- sendProgressReport t $ WorkDoneProgressBegin L. AString title cancellable' msg pct
167
- update t =
168
- forever $ do
169
- -- See Note [Delayed progress reporting]
170
- liftIO $ threadDelay updateDelay
171
- (ProgressAmount pct msg) <- liftIO $ atomically $ takeTMVar reportVar
172
- sendProgressReport t $ WorkDoneProgressReport L. AString Nothing msg pct
173
- end t = sendProgressReport t (WorkDoneProgressEnd L. AString Nothing )
156
+ sendProgressReport t $ WorkDoneProgressReport L. AString Nothing msg pct
157
+ end t = sendProgressReport t (WorkDoneProgressEnd L. AString Nothing )
158
+
159
+ begin t = do
160
+ sendProgressReport t $ WorkDoneProgressBegin L. AString title cancellable' Nothing Nothing
161
+ return t
162
+
163
+ cancellable' = case cancellable of
164
+ Cancellable -> Just True
165
+ NotCancellable -> Just False
166
+
167
+ -- if we have no delays then we can use uninterruptibleMask_ to create the token
168
+ -- to ensure we always get begin and end messages
169
+ maskTokenCreation =
170
+ if startDelay == 0 && updateDelay == 0
171
+ then UE. uninterruptibleMask_
172
+ else id
174
173
175
174
-- Create the token and then start sending reports; all of which races with the check for the
176
175
-- progress having ended. In all cases, make sure to unregister the token at the end.
177
- progressThreads :: m ()
178
- progressThreads =
179
- ((createToken >> sendReports) `UE.finally` unregisterToken) `U.race_` liftIO progressEnded
180
-
176
+ progressThreads runInBase =
177
+ runInBase
178
+ ( UE. bracket
179
+ ( maskTokenCreation $
180
+ createToken
181
+ >> liftIO (atomically $ readTMVar tokenVar)
182
+ >>= begin
183
+ )
184
+ ( \ t -> end t >> unregisterToken t
185
+ )
186
+ update
187
+ )
188
+ `race_` progressEnded
181
189
withRunInIO $ \ runInBase -> do
182
190
withAsync (runInBase $ f updater) $ \ mainAct ->
183
191
-- If the progress gets cancelled then we need to get cancelled too
184
- withAsync (runInBase progressThreads) $ \ pthreads -> do
192
+ withAsync (progressThreads runInBase ) $ \ pthreads -> do
185
193
r <- waitEither mainAct pthreads
186
194
-- TODO: is this weird? I can't see how else to gracefully use the ending barrier
187
195
-- as a guard to cancel the other async
0 commit comments