1use crate::services::terminal::TerminalId;
14use crate::view::file_tree::{FileTreeView, NodeId};
15use lsp_types::{
16 CodeActionOrCommand, CompletionItem, Diagnostic, InlayHint, Location,
17 SemanticTokensFullDeltaResult, SemanticTokensLegend, SemanticTokensRangeResult,
18 SemanticTokensResult, SignatureHelp,
19};
20use serde_json::Value;
21use std::sync::mpsc;
22
23#[derive(Debug)]
25pub enum LspSemanticTokensResponse {
26 Full(Result<Option<SemanticTokensResult>, String>),
27 FullDelta(Result<Option<SemanticTokensFullDeltaResult>, String>),
28 Range(Result<Option<SemanticTokensRangeResult>, String>),
29}
30
31#[derive(Debug)]
33pub enum AsyncMessage {
34 LspDiagnostics {
36 uri: String,
37 diagnostics: Vec<Diagnostic>,
38 },
39
40 LspInitialized {
42 language: String,
43 completion_trigger_characters: Vec<String>,
45 semantic_tokens_legend: Option<SemanticTokensLegend>,
47 semantic_tokens_full: bool,
49 semantic_tokens_full_delta: bool,
51 semantic_tokens_range: bool,
53 },
54
55 LspError {
57 language: String,
58 error: String,
59 stderr_log_path: Option<std::path::PathBuf>,
61 },
62
63 LspCompletion {
65 request_id: u64,
66 items: Vec<CompletionItem>,
67 },
68
69 LspGotoDefinition {
71 request_id: u64,
72 locations: Vec<Location>,
73 },
74
75 LspRename {
77 request_id: u64,
78 result: Result<lsp_types::WorkspaceEdit, String>,
79 },
80
81 LspHover {
83 request_id: u64,
84 contents: String,
86 is_markdown: bool,
88 range: Option<((u32, u32), (u32, u32))>,
91 },
92
93 LspReferences {
95 request_id: u64,
96 locations: Vec<Location>,
97 },
98
99 LspSignatureHelp {
101 request_id: u64,
102 signature_help: Option<SignatureHelp>,
103 },
104
105 LspCodeActions {
107 request_id: u64,
108 actions: Vec<CodeActionOrCommand>,
109 },
110
111 LspPulledDiagnostics {
113 request_id: u64,
114 uri: String,
115 result_id: Option<String>,
117 diagnostics: Vec<Diagnostic>,
119 unchanged: bool,
121 },
122
123 LspInlayHints {
125 request_id: u64,
126 uri: String,
127 hints: Vec<InlayHint>,
129 },
130
131 LspSemanticTokens {
133 request_id: u64,
134 uri: String,
135 response: LspSemanticTokensResponse,
136 },
137
138 LspServerQuiescent { language: String },
141
142 FileChanged { path: String },
144
145 GitStatusChanged { status: String },
147
148 FileExplorerInitialized(FileTreeView),
150
151 FileExplorerToggleNode(NodeId),
153
154 FileExplorerRefreshNode(NodeId),
156
157 FileExplorerExpandedToPath(FileTreeView),
160
161 Plugin(fresh_core::api::PluginAsyncMessage),
163
164 FileOpenDirectoryLoaded(std::io::Result<Vec<crate::services::fs::FsEntry>>),
166
167 TerminalOutput { terminal_id: TerminalId },
169
170 TerminalExited { terminal_id: TerminalId },
172
173 LspProgress {
175 language: String,
176 token: String,
177 value: LspProgressValue,
178 },
179
180 LspWindowMessage {
182 language: String,
183 message_type: LspMessageType,
184 message: String,
185 },
186
187 LspLogMessage {
189 language: String,
190 message_type: LspMessageType,
191 message: String,
192 },
193
194 LspServerRequest {
197 language: String,
198 server_command: String,
199 method: String,
200 params: Option<Value>,
201 },
202
203 PluginLspResponse {
205 language: String,
206 request_id: u64,
207 result: Result<Value, String>,
208 },
209
210 PluginProcessOutput {
212 process_id: u64,
214 stdout: String,
216 stderr: String,
218 exit_code: i32,
220 },
221
222 LspStatusUpdate {
224 language: String,
225 status: LspServerStatus,
226 message: Option<String>,
227 },
228}
229
230#[derive(Debug, Clone)]
232pub enum LspProgressValue {
233 Begin {
234 title: String,
235 message: Option<String>,
236 percentage: Option<u32>,
237 },
238 Report {
239 message: Option<String>,
240 percentage: Option<u32>,
241 },
242 End {
243 message: Option<String>,
244 },
245}
246
247#[derive(Debug, Clone, Copy, PartialEq, Eq)]
249pub enum LspMessageType {
250 Error = 1,
251 Warning = 2,
252 Info = 3,
253 Log = 4,
254}
255
256#[derive(Debug, Clone, Copy, PartialEq, Eq)]
258pub enum LspServerStatus {
259 Starting,
260 Initializing,
261 Running,
262 Error,
263 Shutdown,
264}
265
266#[derive(Clone)]
273pub struct AsyncBridge {
274 sender: mpsc::Sender<AsyncMessage>,
275 receiver: std::sync::Arc<std::sync::Mutex<mpsc::Receiver<AsyncMessage>>>,
277}
278
279impl AsyncBridge {
280 pub fn new() -> Self {
287 let (sender, receiver) = mpsc::channel();
288 Self {
289 sender,
290 receiver: std::sync::Arc::new(std::sync::Mutex::new(receiver)),
291 }
292 }
293
294 pub fn sender(&self) -> mpsc::Sender<AsyncMessage> {
301 self.sender.clone()
302 }
303
304 pub fn try_recv_all(&self) -> Vec<AsyncMessage> {
309 let mut messages = Vec::new();
310
311 if let Ok(receiver) = self.receiver.lock() {
313 while let Ok(msg) = receiver.try_recv() {
314 messages.push(msg);
315 }
316 }
317
318 messages
319 }
320
321 pub fn has_messages(&self) -> bool {
323 if let Ok(receiver) = self.receiver.lock() {
325 receiver.try_recv().is_ok()
326 } else {
327 false
328 }
329 }
330}
331
332impl Default for AsyncBridge {
333 fn default() -> Self {
334 Self::new()
335 }
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341
342 #[test]
343 fn test_async_bridge_send_receive() {
344 let bridge = AsyncBridge::new();
345 let sender = bridge.sender();
346
347 sender
349 .send(AsyncMessage::LspInitialized {
350 language: "rust".to_string(),
351 completion_trigger_characters: vec![".".to_string()],
352 semantic_tokens_legend: None,
353 semantic_tokens_full: false,
354 semantic_tokens_full_delta: false,
355 semantic_tokens_range: false,
356 })
357 .unwrap();
358
359 let messages = bridge.try_recv_all();
361 assert_eq!(messages.len(), 1);
362
363 match &messages[0] {
364 AsyncMessage::LspInitialized {
365 language,
366 completion_trigger_characters,
367 ..
368 } => {
369 assert_eq!(language, "rust");
370 assert_eq!(completion_trigger_characters, &vec![".".to_string()]);
371 }
372 _ => panic!("Wrong message type"),
373 }
374 }
375
376 #[test]
377 fn test_async_bridge_multiple_messages() {
378 let bridge = AsyncBridge::new();
379 let sender = bridge.sender();
380
381 sender
383 .send(AsyncMessage::LspInitialized {
384 language: "rust".to_string(),
385 completion_trigger_characters: vec![],
386 semantic_tokens_legend: None,
387 semantic_tokens_full: false,
388 semantic_tokens_full_delta: false,
389 semantic_tokens_range: false,
390 })
391 .unwrap();
392 sender
393 .send(AsyncMessage::LspInitialized {
394 language: "typescript".to_string(),
395 completion_trigger_characters: vec![],
396 semantic_tokens_legend: None,
397 semantic_tokens_full: false,
398 semantic_tokens_full_delta: false,
399 semantic_tokens_range: false,
400 })
401 .unwrap();
402
403 let messages = bridge.try_recv_all();
405 assert_eq!(messages.len(), 2);
406 }
407
408 #[test]
409 fn test_async_bridge_no_messages() {
410 let bridge = AsyncBridge::new();
411
412 let messages = bridge.try_recv_all();
414 assert_eq!(messages.len(), 0);
415 }
416
417 #[test]
418 fn test_async_bridge_clone_sender() {
419 let bridge = AsyncBridge::new();
420 let sender1 = bridge.sender();
421 let sender2 = sender1.clone();
422
423 sender1
425 .send(AsyncMessage::LspInitialized {
426 language: "rust".to_string(),
427 completion_trigger_characters: vec![],
428 semantic_tokens_legend: None,
429 semantic_tokens_full: false,
430 semantic_tokens_full_delta: false,
431 semantic_tokens_range: false,
432 })
433 .unwrap();
434 sender2
435 .send(AsyncMessage::LspInitialized {
436 language: "typescript".to_string(),
437 completion_trigger_characters: vec![],
438 semantic_tokens_legend: None,
439 semantic_tokens_full: false,
440 semantic_tokens_full_delta: false,
441 semantic_tokens_range: false,
442 })
443 .unwrap();
444
445 let messages = bridge.try_recv_all();
446 assert_eq!(messages.len(), 2);
447 }
448
449 #[test]
450 fn test_async_bridge_diagnostics() {
451 let bridge = AsyncBridge::new();
452 let sender = bridge.sender();
453
454 let diagnostics = vec![lsp_types::Diagnostic {
456 range: lsp_types::Range {
457 start: lsp_types::Position {
458 line: 0,
459 character: 0,
460 },
461 end: lsp_types::Position {
462 line: 0,
463 character: 5,
464 },
465 },
466 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
467 code: None,
468 code_description: None,
469 source: Some("rust-analyzer".to_string()),
470 message: "test error".to_string(),
471 related_information: None,
472 tags: None,
473 data: None,
474 }];
475
476 sender
477 .send(AsyncMessage::LspDiagnostics {
478 uri: "file:///test.rs".to_string(),
479 diagnostics: diagnostics.clone(),
480 })
481 .unwrap();
482
483 let messages = bridge.try_recv_all();
484 assert_eq!(messages.len(), 1);
485
486 match &messages[0] {
487 AsyncMessage::LspDiagnostics {
488 uri,
489 diagnostics: diags,
490 } => {
491 assert_eq!(uri, "file:///test.rs");
492 assert_eq!(diags.len(), 1);
493 assert_eq!(diags[0].message, "test error");
494 }
495 _ => panic!("Expected LspDiagnostics message"),
496 }
497 }
498
499 #[test]
500 fn test_async_bridge_error_message() {
501 let bridge = AsyncBridge::new();
502 let sender = bridge.sender();
503
504 sender
505 .send(AsyncMessage::LspError {
506 language: "rust".to_string(),
507 error: "Failed to initialize".to_string(),
508 stderr_log_path: None,
509 })
510 .unwrap();
511
512 let messages = bridge.try_recv_all();
513 assert_eq!(messages.len(), 1);
514
515 match &messages[0] {
516 AsyncMessage::LspError {
517 language,
518 error,
519 stderr_log_path,
520 } => {
521 assert_eq!(language, "rust");
522 assert_eq!(error, "Failed to initialize");
523 assert!(stderr_log_path.is_none());
524 }
525 _ => panic!("Expected LspError message"),
526 }
527 }
528
529 #[test]
530 fn test_async_bridge_clone_bridge() {
531 let bridge = AsyncBridge::new();
532 let bridge_clone = bridge.clone();
533 let sender = bridge.sender();
534
535 sender
537 .send(AsyncMessage::LspInitialized {
538 language: "rust".to_string(),
539 completion_trigger_characters: vec![],
540 semantic_tokens_legend: None,
541 semantic_tokens_full: false,
542 semantic_tokens_full_delta: false,
543 semantic_tokens_range: false,
544 })
545 .unwrap();
546
547 let messages = bridge_clone.try_recv_all();
549 assert_eq!(messages.len(), 1);
550 }
551
552 #[test]
553 fn test_async_bridge_multiple_calls_to_try_recv_all() {
554 let bridge = AsyncBridge::new();
555 let sender = bridge.sender();
556
557 sender
558 .send(AsyncMessage::LspInitialized {
559 language: "rust".to_string(),
560 completion_trigger_characters: vec![],
561 semantic_tokens_legend: None,
562 semantic_tokens_full: false,
563 semantic_tokens_full_delta: false,
564 semantic_tokens_range: false,
565 })
566 .unwrap();
567
568 let messages1 = bridge.try_recv_all();
570 assert_eq!(messages1.len(), 1);
571
572 let messages2 = bridge.try_recv_all();
574 assert_eq!(messages2.len(), 0);
575 }
576
577 #[test]
578 fn test_async_bridge_ordering() {
579 let bridge = AsyncBridge::new();
580 let sender = bridge.sender();
581
582 sender
584 .send(AsyncMessage::LspInitialized {
585 language: "rust".to_string(),
586 completion_trigger_characters: vec![],
587 semantic_tokens_legend: None,
588 semantic_tokens_full: false,
589 semantic_tokens_full_delta: false,
590 semantic_tokens_range: false,
591 })
592 .unwrap();
593 sender
594 .send(AsyncMessage::LspInitialized {
595 language: "typescript".to_string(),
596 completion_trigger_characters: vec![],
597 semantic_tokens_legend: None,
598 semantic_tokens_full: false,
599 semantic_tokens_full_delta: false,
600 semantic_tokens_range: false,
601 })
602 .unwrap();
603 sender
604 .send(AsyncMessage::LspInitialized {
605 language: "python".to_string(),
606 completion_trigger_characters: vec![],
607 semantic_tokens_legend: None,
608 semantic_tokens_full: false,
609 semantic_tokens_full_delta: false,
610 semantic_tokens_range: false,
611 })
612 .unwrap();
613
614 let messages = bridge.try_recv_all();
616 assert_eq!(messages.len(), 3);
617
618 match (&messages[0], &messages[1], &messages[2]) {
619 (
620 AsyncMessage::LspInitialized { language: l1, .. },
621 AsyncMessage::LspInitialized { language: l2, .. },
622 AsyncMessage::LspInitialized { language: l3, .. },
623 ) => {
624 assert_eq!(l1, "rust");
625 assert_eq!(l2, "typescript");
626 assert_eq!(l3, "python");
627 }
628 _ => panic!("Expected ordered LspInitialized messages"),
629 }
630 }
631}