tower_lsp_server/service/client/
progress.rs

1//! Types for emitting `$/progress` notifications to the client.
2
3use std::fmt::{self, Debug, Formatter};
4use std::marker::PhantomData;
5
6use lsp_types::{
7    ProgressParams, ProgressParamsValue, ProgressToken, WorkDoneProgress, WorkDoneProgressBegin,
8    WorkDoneProgressReport, notification::Progress as ProgressNotification,
9};
10
11use super::Client;
12
13/// Indicates the progress stream is bounded from 0-100%.
14#[doc(hidden)]
15#[derive(Debug)]
16pub enum Bounded {}
17
18/// Indicates the progress stream is unbounded.
19#[doc(hidden)]
20#[derive(Debug)]
21pub enum Unbounded {}
22
23/// Indicates the progress stream may be canceled by the client.
24#[doc(hidden)]
25#[derive(Debug)]
26pub enum Cancellable {}
27
28/// Indicates the progress stream cannot be canceled by the client.
29#[doc(hidden)]
30#[derive(Debug)]
31pub enum NotCancellable {}
32
33/// A builder for a new `$/progress` stream.
34///
35/// This progress stream is initially assumed to be _unbounded_ and _not cancellable_.
36///
37/// This struct is created by [`Client::progress`]. See its documentation for more.
38#[must_use = "progress is not reported until `.begin()` is called"]
39pub struct Progress<B = Unbounded, C = NotCancellable> {
40    client: Client,
41    token: ProgressToken,
42    begin_msg: WorkDoneProgressBegin,
43    _kind: PhantomData<(B, C)>,
44}
45
46impl Progress {
47    pub(crate) const fn new(client: Client, token: ProgressToken, title: String) -> Self {
48        Self {
49            client,
50            token,
51            begin_msg: WorkDoneProgressBegin {
52                title,
53                cancellable: Some(false),
54                message: None,
55                percentage: None,
56            },
57            _kind: PhantomData,
58        }
59    }
60}
61
62impl<C> Progress<Unbounded, C> {
63    /// Sets the optional progress percentage to display in the client UI.
64    ///
65    /// This percentage value is initially `start_percentage`, where a value of `100` for example
66    /// is considered 100% by the client. If this method is not called, unbounded progress is
67    /// assumed.
68    pub fn with_percentage(self, start_percentage: u32) -> Progress<Bounded, C> {
69        Progress {
70            client: self.client,
71            token: self.token,
72            begin_msg: WorkDoneProgressBegin {
73                percentage: Some(start_percentage),
74                ..self.begin_msg
75            },
76            _kind: PhantomData,
77        }
78    }
79}
80
81impl<B> Progress<B, NotCancellable> {
82    /// Indicates that a "cancel" button should be displayed in the client UI.
83    ///
84    /// Clients that don’t support cancellation are allowed to ignore this setting. If this method
85    /// is not called, the user will not be presented with an option to cancel this operation.
86    pub fn with_cancel_button(self) -> Progress<B, Cancellable> {
87        Progress {
88            client: self.client,
89            token: self.token,
90            begin_msg: WorkDoneProgressBegin {
91                cancellable: Some(true),
92                ..self.begin_msg
93            },
94            _kind: PhantomData,
95        }
96    }
97}
98
99impl<B, C> Progress<B, C> {
100    /// Includes an optional more detailed progress message.
101    ///
102    /// This message is expected to contain information complementary to the `title` string passed
103    /// into [`Client::progress`], such as `"3/25 files"`, `"project/src/module2"`, or
104    /// `"node_modules/some_dep"`.
105    pub fn with_message<M>(mut self, message: M) -> Self
106    where
107        M: Into<String>,
108    {
109        self.begin_msg.message = Some(message.into());
110        self
111    }
112
113    /// Starts reporting progress to the client, returning an [`OngoingProgress`] handle.
114    ///
115    /// # Initialization
116    ///
117    /// This notification will only be sent if the server is initialized.
118    pub async fn begin(self) -> OngoingProgress<B, C> {
119        self.client
120            .send_notification::<ProgressNotification>(ProgressParams {
121                token: self.token.clone(),
122                value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(self.begin_msg)),
123            })
124            .await;
125
126        OngoingProgress {
127            client: self.client,
128            token: self.token,
129            _kind: PhantomData,
130        }
131    }
132}
133
134impl<B, C> Debug for Progress<B, C> {
135    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
136        f.debug_struct(stringify!(Progress))
137            .field("token", &self.token)
138            .field("properties", &self.begin_msg)
139            .finish_non_exhaustive()
140    }
141}
142
143/// An ongoing stream of progress being reported to the client.
144///
145/// This struct is created by [`Progress::begin`]. See its documentation for more.
146#[must_use = "ongoing progress is not reported until `.report()` and/or `.finish()` is called"]
147pub struct OngoingProgress<B, C> {
148    client: Client,
149    token: ProgressToken,
150    _kind: PhantomData<(B, C)>,
151}
152
153impl<B: Sync, C: Sync> OngoingProgress<B, C> {
154    async fn send_progress_report(&self, report: WorkDoneProgressReport) {
155        self.client
156            .send_notification::<ProgressNotification>(ProgressParams {
157                token: self.token.clone(),
158                value: ProgressParamsValue::WorkDone(WorkDoneProgress::Report(report)),
159            })
160            .await;
161    }
162}
163
164impl OngoingProgress<Unbounded, NotCancellable> {
165    /// Updates the secondary progress message visible in the client UI.
166    ///
167    /// This message is expected to contain information complementary to the `title` string passed
168    /// into [`Client::progress`], such as `"3/25 files"`, `"project/src/module2"`, or
169    /// `"node_modules/some_dep"`.
170    ///
171    /// # Initialization
172    ///
173    /// This notification will only be sent if the server is initialized.
174    pub async fn report<M>(&self, message: M)
175    where
176        M: Into<String>,
177    {
178        self.send_progress_report(WorkDoneProgressReport {
179            message: Some(message.into()),
180            ..Default::default()
181        })
182        .await;
183    }
184}
185
186impl OngoingProgress<Unbounded, Cancellable> {
187    /// Enables or disables the "cancel" button in the client UI.
188    ///
189    /// # Initialization
190    ///
191    /// This notification will only be sent if the server is initialized.
192    pub async fn report(&self, enable_cancel_btn: bool) {
193        self.send_progress_report(WorkDoneProgressReport {
194            cancellable: Some(enable_cancel_btn),
195            ..Default::default()
196        })
197        .await;
198    }
199
200    /// Updates the secondary progress message visible in the client UI and optionally
201    /// enables/disables the "cancel" button.
202    ///
203    /// This message is expected to contain information complementary to the `title` string passed
204    /// into [`Client::progress`], such as `"3/25 files"`, `"project/src/module2"`, or
205    /// `"node_modules/some_dep"`.
206    ///
207    /// If `enable_cancel_btn` is `None`, the state of the "cancel" button in the UI is unchanged.
208    ///
209    /// # Initialization
210    ///
211    /// This notification will only be sent if the server is initialized.
212    pub async fn report_with_message<M>(&self, message: M, enable_cancel_btn: Option<bool>)
213    where
214        M: Into<String>,
215    {
216        self.send_progress_report(WorkDoneProgressReport {
217            cancellable: enable_cancel_btn,
218            message: Some(message.into()),
219            ..Default::default()
220        })
221        .await;
222    }
223}
224
225impl OngoingProgress<Bounded, NotCancellable> {
226    /// Updates the progress percentage displayed in the client UI, where a value of `100` for
227    /// example is considered 100% by the client.
228    ///
229    /// # Initialization
230    ///
231    /// This notification will only be sent if the server is initialized.
232    pub async fn report(&self, percentage: u32) {
233        self.send_progress_report(WorkDoneProgressReport {
234            percentage: Some(percentage),
235            ..Default::default()
236        })
237        .await;
238    }
239
240    /// Same as [`OngoingProgress::report`](OngoingProgress#method.report-2), except it also
241    /// displays an optional more detailed progress message.
242    ///
243    /// This message is expected to contain information complementary to the `title` string passed
244    /// into [`Client::progress`], such as `"3/25 files"`, `"project/src/module2"`, or
245    /// `"node_modules/some_dep"`.
246    ///
247    /// # Initialization
248    ///
249    /// This notification will only be sent if the server is initialized.
250    pub async fn report_with_message<M>(&self, message: M, percentage: u32)
251    where
252        M: Into<String>,
253    {
254        self.send_progress_report(WorkDoneProgressReport {
255            message: Some(message.into()),
256            percentage: Some(percentage),
257            ..Default::default()
258        })
259        .await;
260    }
261}
262
263impl OngoingProgress<Bounded, Cancellable> {
264    /// Updates the progress percentage displayed in the client UI, where a value of `100` for
265    /// example is considered 100% by the client.
266    ///
267    /// If `enable_cancel_btn` is `None`, the state of the "cancel" button in the UI is unchanged.
268    ///
269    /// # Initialization
270    ///
271    /// This notification will only be sent if the server is initialized.
272    pub async fn report(&self, percentage: u32, enable_cancel_btn: Option<bool>) {
273        self.send_progress_report(WorkDoneProgressReport {
274            cancellable: enable_cancel_btn,
275            message: None,
276            percentage: Some(percentage),
277        })
278        .await;
279    }
280
281    /// Same as [`OngoingProgress::report`](OngoingProgress#method.report-3), except it also
282    /// displays an optional more detailed progress message.
283    ///
284    /// This message is expected to contain information complementary to the `title` string passed
285    /// into [`Client::progress`], such as `"3/25 files"`, `"project/src/module2"`, or
286    /// `"node_modules/some_dep"`.
287    ///
288    /// # Initialization
289    ///
290    /// This notification will only be sent if the server is initialized.
291    pub async fn report_with_message<M>(
292        &self,
293        message: M,
294        percentage: u32,
295        enable_cancel_btn: Option<bool>,
296    ) where
297        M: Into<String>,
298    {
299        self.send_progress_report(WorkDoneProgressReport {
300            cancellable: enable_cancel_btn,
301            message: Some(message.into()),
302            percentage: Some(percentage),
303        })
304        .await;
305    }
306}
307
308impl<C> OngoingProgress<Bounded, C> {
309    /// Discards the progress bound associated with this `OngoingProgress`.
310    ///
311    /// All subsequent progress reports will no longer show a percentage value.
312    pub fn into_unbounded(self) -> OngoingProgress<Unbounded, C> {
313        OngoingProgress {
314            client: self.client,
315            token: self.token,
316            _kind: PhantomData,
317        }
318    }
319}
320
321impl<B, C> OngoingProgress<B, C> {
322    /// Indicates this long-running operation is complete.
323    ///
324    /// # Initialization
325    ///
326    /// This notification will only be sent if the server is initialized.
327    pub async fn finish(self) {
328        self.finish_inner(None).await;
329    }
330
331    /// Same as [`OngoingProgress::finish`], except it also displays an optional more detailed
332    /// progress message.
333    ///
334    /// This message is expected to contain information complementary to the `title` string passed
335    /// into [`Client::progress`], such as `"3/25 files"`, `"project/src/module2"`, or
336    /// `"node_modules/some_dep"`.
337    ///
338    /// # Initialization
339    ///
340    /// This notification will only be sent if the server is initialized.
341    pub async fn finish_with_message<M>(self, message: M)
342    where
343        M: Into<String>,
344    {
345        self.finish_inner(Some(message.into())).await;
346    }
347
348    async fn finish_inner(self, message: Option<String>) {
349        self.client
350            .send_notification::<ProgressNotification>(ProgressParams {
351                token: self.token,
352                value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(
353                    lsp_types::WorkDoneProgressEnd { message },
354                )),
355            })
356            .await;
357    }
358
359    /// Returns the `ProgressToken` associated with this long-running operation.
360    #[must_use]
361    pub const fn token(&self) -> &ProgressToken {
362        &self.token
363    }
364}
365
366impl<B, C> Debug for OngoingProgress<B, C> {
367    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
368        f.debug_struct(stringify!(OngoingProgress))
369            .field("token", &self.token)
370            .finish_non_exhaustive()
371    }
372}