1use std::sync::Arc;
7use std::time::Instant;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum LspInitStatus {
12 NotStarted,
14 Spawning,
16 Initializing,
18 Ready,
20 Failed,
22}
23
24impl LspInitStatus {
25 pub fn label(&self) -> &'static str {
27 match self {
28 Self::NotStarted => "Not Started",
29 Self::Spawning => "Starting Process",
30 Self::Initializing => "Initializing",
31 Self::Ready => "Ready",
32 Self::Failed => "Failed",
33 }
34 }
35
36 pub fn is_complete(&self) -> bool {
38 matches!(self, Self::Ready | Self::Failed)
39 }
40
41 pub fn is_ready(&self) -> bool {
43 matches!(self, Self::Ready)
44 }
45
46 pub fn is_in_progress(&self) -> bool {
48 matches!(self, Self::Spawning | Self::Initializing)
49 }
50}
51
52pub struct LspProgressTracker {
56 language: String,
58 server_name: String,
60 status: LspInitStatus,
62 progress: f32,
64 message: String,
66 started_at: Option<Instant>,
68}
69
70impl LspProgressTracker {
71 pub fn new(language: impl Into<String>, server_name: impl Into<String>) -> Self {
73 Self {
74 language: language.into(),
75 server_name: server_name.into(),
76 status: LspInitStatus::NotStarted,
77 progress: 0.0,
78 message: "Waiting to start...".to_string(),
79 started_at: None,
80 }
81 }
82
83 pub fn language(&self) -> &str {
85 &self.language
86 }
87
88 pub fn server_name(&self) -> &str {
90 &self.server_name
91 }
92
93 pub fn status(&self) -> LspInitStatus {
95 self.status
96 }
97
98 pub fn progress(&self) -> f32 {
100 self.progress
101 }
102
103 pub fn message(&self) -> &str {
105 &self.message
106 }
107
108 pub fn elapsed_secs(&self) -> Option<f64> {
110 self.started_at.map(|t| t.elapsed().as_secs_f64())
111 }
112
113 pub fn is_ready(&self) -> bool {
115 self.status.is_ready()
116 }
117
118 pub fn start(&mut self) {
120 self.status = LspInitStatus::Spawning;
121 self.progress = 0.0;
122 self.message = "Starting process...".to_string();
123 self.started_at = Some(Instant::now());
124 }
125
126 pub fn update(&mut self, progress: f32, message: impl Into<String>) {
128 if progress > 0.0 && progress <= 1.0 {
129 self.progress = progress;
130 self.message = message.into();
131
132 if progress < 0.3 {
134 self.status = LspInitStatus::Spawning;
135 } else if progress < 1.0 {
136 self.status = LspInitStatus::Initializing;
137 }
138 }
139 }
140
141 pub fn complete(&mut self) {
143 self.status = LspInitStatus::Ready;
144 self.progress = 1.0;
145 self.message = "Ready".to_string();
146 }
147
148 pub fn fail(&mut self, error: impl Into<String>) {
150 self.status = LspInitStatus::Failed;
151 self.progress = 0.0;
152 self.message = error.into();
153 }
154}
155
156pub trait LspProgressCallback: Send + Sync {
161 fn on_progress(&self, progress: f32, message: &str);
166
167 fn on_complete(&self);
169
170 fn on_error(&self, error: &str);
174}
175
176pub struct NoOpProgressCallback;
178
179impl LspProgressCallback for NoOpProgressCallback {
180 fn on_progress(&self, _progress: f32, _message: &str) {}
181 fn on_complete(&self) {}
182 fn on_error(&self, _error: &str) {}
183}
184
185pub struct LoggingProgressCallback;
189
190impl LspProgressCallback for LoggingProgressCallback {
191 fn on_progress(&self, progress: f32, message: &str) {
192 log::info!("LSP Progress: {:.0}% - {}", progress * 100.0, message);
193 }
194
195 fn on_complete(&self) {
196 log::info!("LSP Initialization complete");
197 }
198
199 fn on_error(&self, error: &str) {
200 log::error!("LSP Initialization failed: {}", error);
201 }
202}
203
204pub struct MultiProgressCallback {
208 callbacks: Vec<Arc<dyn LspProgressCallback>>,
209}
210
211impl MultiProgressCallback {
212 pub fn new(callbacks: Vec<Arc<dyn LspProgressCallback>>) -> Self {
214 Self { callbacks }
215 }
216
217 pub fn add(&mut self, callback: Arc<dyn LspProgressCallback>) {
219 self.callbacks.push(callback);
220 }
221}
222
223impl LspProgressCallback for MultiProgressCallback {
224 fn on_progress(&self, progress: f32, message: &str) {
225 for callback in &self.callbacks {
226 callback.on_progress(progress, message);
227 }
228 }
229
230 fn on_complete(&self) {
231 for callback in &self.callbacks {
232 callback.on_complete();
233 }
234 }
235
236 fn on_error(&self, error: &str) {
237 for callback in &self.callbacks {
238 callback.on_error(error);
239 }
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[test]
248 fn test_status_labels() {
249 assert_eq!(LspInitStatus::NotStarted.label(), "Not Started");
250 assert_eq!(LspInitStatus::Spawning.label(), "Starting Process");
251 assert_eq!(LspInitStatus::Initializing.label(), "Initializing");
252 assert_eq!(LspInitStatus::Ready.label(), "Ready");
253 assert_eq!(LspInitStatus::Failed.label(), "Failed");
254 }
255
256 #[test]
257 fn test_status_checks() {
258 assert!(LspInitStatus::Ready.is_complete());
259 assert!(LspInitStatus::Failed.is_complete());
260 assert!(!LspInitStatus::Initializing.is_complete());
261
262 assert!(LspInitStatus::Ready.is_ready());
263 assert!(!LspInitStatus::Failed.is_ready());
264
265 assert!(LspInitStatus::Spawning.is_in_progress());
266 assert!(LspInitStatus::Initializing.is_in_progress());
267 assert!(!LspInitStatus::Ready.is_in_progress());
268 }
269
270 #[test]
271 fn test_progress_tracker_lifecycle() {
272 let mut tracker = LspProgressTracker::new("rust", "rust-analyzer");
273
274 assert_eq!(tracker.status(), LspInitStatus::NotStarted);
276 assert_eq!(tracker.progress(), 0.0);
277 assert_eq!(tracker.message(), "Waiting to start...");
278 assert!(tracker.elapsed_secs().is_none());
279
280 tracker.start();
282 assert_eq!(tracker.status(), LspInitStatus::Spawning);
283 assert_eq!(tracker.progress(), 0.0);
284 assert!(tracker.elapsed_secs().is_some());
285
286 tracker.update(0.5, "Loading workspace...");
288 assert_eq!(tracker.status(), LspInitStatus::Initializing);
289 assert_eq!(tracker.progress(), 0.5);
290 assert_eq!(tracker.message(), "Loading workspace...");
291
292 tracker.complete();
294 assert_eq!(tracker.status(), LspInitStatus::Ready);
295 assert_eq!(tracker.progress(), 1.0);
296 assert_eq!(tracker.message(), "Ready");
297 }
298
299 #[test]
300 fn test_progress_tracker_failure() {
301 let mut tracker = LspProgressTracker::new("typescript", "typescript-language-server");
302 tracker.start();
303 tracker.update(0.3, "Initializing...");
304
305 tracker.fail("Process spawn failed");
306 assert_eq!(tracker.status(), LspInitStatus::Failed);
307 assert_eq!(tracker.message(), "Process spawn failed");
308 assert!(!tracker.is_ready());
309 }
310
311 #[test]
312 fn test_no_op_callback() {
313 let callback = NoOpProgressCallback;
314 callback.on_progress(0.5, "test");
315 callback.on_complete();
316 callback.on_error("test error");
317 }
319
320 #[test]
321 fn test_multi_callback() {
322 use std::sync::Mutex;
323
324 struct TestCallback {
325 progress_calls: Mutex<Vec<(f32, String)>>,
326 }
327
328 impl LspProgressCallback for TestCallback {
329 fn on_progress(&self, progress: f32, message: &str) {
330 self.progress_calls.lock().unwrap().push((progress, message.to_string()));
331 }
332 fn on_complete(&self) {}
333 fn on_error(&self, _error: &str) {}
334 }
335
336 let cb1 = Arc::new(TestCallback {
337 progress_calls: Mutex::new(Vec::new()),
338 });
339 let cb2 = Arc::new(NoOpProgressCallback);
340
341 let multi = MultiProgressCallback::new(vec![cb1.clone(), cb2]);
342 multi.on_progress(0.5, "test");
343
344 let calls = cb1.progress_calls.lock().unwrap();
345 assert_eq!(calls.len(), 1);
346 assert_eq!(calls[0], (0.5, "test".to_string()));
347 }
348}