Skip to main content

lspkit_server/
progress.rs

1//! Work-done progress sink.
2//!
3//! Wraps the LSP `window/workDoneProgress` protocol. The sink is a no-op when
4//! the client did not advertise the capability — long-running operations can
5//! report unconditionally without checking.
6
7use std::sync::Arc;
8
9use async_trait::async_trait;
10
11/// One progress step.
12#[non_exhaustive]
13#[derive(Debug, Clone)]
14pub struct ProgressUpdate {
15    /// Token identifying the progress stream.
16    pub token: String,
17    /// Human-readable message.
18    pub message: String,
19    /// Optional `[0, 100]` percentage.
20    pub percentage: Option<u32>,
21    /// `true` when this is the final update for the stream.
22    pub done: bool,
23}
24
25impl ProgressUpdate {
26    /// Construct an in-progress update.
27    #[must_use]
28    pub fn new(
29        token: impl Into<String>,
30        message: impl Into<String>,
31        percentage: Option<u32>,
32    ) -> Self {
33        Self {
34            token: token.into(),
35            message: message.into(),
36            percentage,
37            done: false,
38        }
39    }
40
41    /// Construct a final update with `percentage` of `100`.
42    #[must_use]
43    pub fn final_update(token: impl Into<String>, message: impl Into<String>) -> Self {
44        Self {
45            token: token.into(),
46            message: message.into(),
47            percentage: Some(100),
48            done: true,
49        }
50    }
51}
52
53/// A receiver for progress updates.
54#[async_trait]
55pub trait ProgressReporter: Send + Sync + 'static {
56    /// Receive an update. Implementations MUST NOT block.
57    async fn report(&self, update: ProgressUpdate);
58}
59
60/// No-op reporter used when the client did not request progress.
61#[derive(Debug, Default, Clone, Copy)]
62pub struct NoopReporter;
63
64#[async_trait]
65impl ProgressReporter for NoopReporter {
66    async fn report(&self, _update: ProgressUpdate) {}
67}
68
69/// A handle that publishes updates to its inner reporter.
70#[derive(Clone)]
71pub struct ProgressSink {
72    inner: Arc<dyn ProgressReporter>,
73    token: String,
74}
75
76impl ProgressSink {
77    /// Construct a sink that forwards updates to `inner` under `token`.
78    pub fn new(inner: Arc<dyn ProgressReporter>, token: impl Into<String>) -> Self {
79        Self {
80            inner,
81            token: token.into(),
82        }
83    }
84
85    /// No-op sink that drops every update.
86    #[must_use]
87    pub fn noop() -> Self {
88        Self {
89            inner: Arc::new(NoopReporter),
90            token: String::new(),
91        }
92    }
93
94    /// Send a single update.
95    pub async fn report(&self, message: impl Into<String>, percentage: Option<u32>) {
96        self.inner
97            .report(ProgressUpdate {
98                token: self.token.clone(),
99                message: message.into(),
100                percentage,
101                done: false,
102            })
103            .await;
104    }
105
106    /// Send a final update and close the stream.
107    pub async fn finish(&self, message: impl Into<String>) {
108        self.inner
109            .report(ProgressUpdate {
110                token: self.token.clone(),
111                message: message.into(),
112                percentage: Some(100),
113                done: true,
114            })
115            .await;
116    }
117}
118
119impl std::fmt::Debug for ProgressSink {
120    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121        f.debug_struct("ProgressSink")
122            .field("token", &self.token)
123            .finish_non_exhaustive()
124    }
125}