1use crate::services::terminal::TerminalId;
14use crate::view::file_tree::{FileTreeView, NodeId};
15use lsp_types::{
16 CodeActionOrCommand, CompletionItem, Diagnostic, FoldingRange, 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 folding_ranges_supported: bool,
55 },
56
57 LspError {
59 language: String,
60 error: String,
61 stderr_log_path: Option<std::path::PathBuf>,
63 },
64
65 LspCompletion {
67 request_id: u64,
68 items: Vec<CompletionItem>,
69 },
70
71 LspGotoDefinition {
73 request_id: u64,
74 locations: Vec<Location>,
75 },
76
77 LspRename {
79 request_id: u64,
80 result: Result<lsp_types::WorkspaceEdit, String>,
81 },
82
83 LspHover {
85 request_id: u64,
86 contents: String,
88 is_markdown: bool,
90 range: Option<((u32, u32), (u32, u32))>,
93 },
94
95 LspReferences {
97 request_id: u64,
98 locations: Vec<Location>,
99 },
100
101 LspSignatureHelp {
103 request_id: u64,
104 signature_help: Option<SignatureHelp>,
105 },
106
107 LspCodeActions {
109 request_id: u64,
110 actions: Vec<CodeActionOrCommand>,
111 },
112
113 LspPulledDiagnostics {
115 request_id: u64,
116 uri: String,
117 result_id: Option<String>,
119 diagnostics: Vec<Diagnostic>,
121 unchanged: bool,
123 },
124
125 LspInlayHints {
127 request_id: u64,
128 uri: String,
129 hints: Vec<InlayHint>,
131 },
132
133 LspFoldingRanges {
135 request_id: u64,
136 uri: String,
137 ranges: Vec<FoldingRange>,
138 },
139
140 LspSemanticTokens {
142 request_id: u64,
143 uri: String,
144 response: LspSemanticTokensResponse,
145 },
146
147 LspServerQuiescent { language: String },
150
151 LspDiagnosticRefresh { language: String },
154
155 FileChanged { path: String },
157
158 GitStatusChanged { status: String },
160
161 FileExplorerInitialized(FileTreeView),
163
164 FileExplorerToggleNode(NodeId),
166
167 FileExplorerRefreshNode(NodeId),
169
170 FileExplorerExpandedToPath(FileTreeView),
173
174 Plugin(fresh_core::api::PluginAsyncMessage),
176
177 FileOpenDirectoryLoaded(std::io::Result<Vec<crate::services::fs::DirEntry>>),
179
180 FileOpenShortcutsLoaded(Vec<crate::app::file_open::NavigationShortcut>),
182
183 TerminalOutput { terminal_id: TerminalId },
185
186 TerminalExited { terminal_id: TerminalId },
188
189 LspProgress {
191 language: String,
192 token: String,
193 value: LspProgressValue,
194 },
195
196 LspWindowMessage {
198 language: String,
199 message_type: LspMessageType,
200 message: String,
201 },
202
203 LspLogMessage {
205 language: String,
206 message_type: LspMessageType,
207 message: String,
208 },
209
210 LspServerRequest {
213 language: String,
214 server_command: String,
215 method: String,
216 params: Option<Value>,
217 },
218
219 PluginLspResponse {
221 language: String,
222 request_id: u64,
223 result: Result<Value, String>,
224 },
225
226 PluginProcessOutput {
228 process_id: u64,
230 stdout: String,
232 stderr: String,
234 exit_code: i32,
236 },
237
238 LspStatusUpdate {
240 language: String,
241 status: LspServerStatus,
242 message: Option<String>,
243 },
244
245 GrammarRegistryBuilt {
249 registry: std::sync::Arc<crate::primitives::grammar::GrammarRegistry>,
250 callback_ids: Vec<fresh_core::api::JsCallbackId>,
251 },
252}
253
254#[derive(Debug, Clone)]
256pub enum LspProgressValue {
257 Begin {
258 title: String,
259 message: Option<String>,
260 percentage: Option<u32>,
261 },
262 Report {
263 message: Option<String>,
264 percentage: Option<u32>,
265 },
266 End {
267 message: Option<String>,
268 },
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
273pub enum LspMessageType {
274 Error = 1,
275 Warning = 2,
276 Info = 3,
277 Log = 4,
278}
279
280#[derive(Debug, Clone, Copy, PartialEq, Eq)]
282pub enum LspServerStatus {
283 Starting,
284 Initializing,
285 Running,
286 Error,
287 Shutdown,
288}
289
290#[derive(Clone)]
297pub struct AsyncBridge {
298 sender: mpsc::Sender<AsyncMessage>,
299 receiver: std::sync::Arc<std::sync::Mutex<mpsc::Receiver<AsyncMessage>>>,
301}
302
303impl AsyncBridge {
304 pub fn new() -> Self {
311 let (sender, receiver) = mpsc::channel();
312 Self {
313 sender,
314 receiver: std::sync::Arc::new(std::sync::Mutex::new(receiver)),
315 }
316 }
317
318 pub fn sender(&self) -> mpsc::Sender<AsyncMessage> {
325 self.sender.clone()
326 }
327
328 pub fn try_recv_all(&self) -> Vec<AsyncMessage> {
333 let mut messages = Vec::new();
334
335 if let Ok(receiver) = self.receiver.lock() {
337 while let Ok(msg) = receiver.try_recv() {
338 messages.push(msg);
339 }
340 }
341
342 messages
343 }
344
345 pub fn has_messages(&self) -> bool {
347 if let Ok(receiver) = self.receiver.lock() {
349 receiver.try_recv().is_ok()
350 } else {
351 false
352 }
353 }
354}
355
356impl Default for AsyncBridge {
357 fn default() -> Self {
358 Self::new()
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365
366 #[test]
367 fn test_async_bridge_send_receive() {
368 let bridge = AsyncBridge::new();
369 let sender = bridge.sender();
370
371 sender
373 .send(AsyncMessage::LspInitialized {
374 language: "rust".to_string(),
375 completion_trigger_characters: vec![".".to_string()],
376 semantic_tokens_legend: None,
377 semantic_tokens_full: false,
378 semantic_tokens_full_delta: false,
379 semantic_tokens_range: false,
380 folding_ranges_supported: false,
381 })
382 .unwrap();
383
384 let messages = bridge.try_recv_all();
386 assert_eq!(messages.len(), 1);
387
388 match &messages[0] {
389 AsyncMessage::LspInitialized {
390 language,
391 completion_trigger_characters,
392 ..
393 } => {
394 assert_eq!(language, "rust");
395 assert_eq!(completion_trigger_characters, &vec![".".to_string()]);
396 }
397 _ => panic!("Wrong message type"),
398 }
399 }
400
401 #[test]
402 fn test_async_bridge_multiple_messages() {
403 let bridge = AsyncBridge::new();
404 let sender = bridge.sender();
405
406 sender
408 .send(AsyncMessage::LspInitialized {
409 language: "rust".to_string(),
410 completion_trigger_characters: vec![],
411 semantic_tokens_legend: None,
412 semantic_tokens_full: false,
413 semantic_tokens_full_delta: false,
414 semantic_tokens_range: false,
415 folding_ranges_supported: false,
416 })
417 .unwrap();
418 sender
419 .send(AsyncMessage::LspInitialized {
420 language: "typescript".to_string(),
421 completion_trigger_characters: vec![],
422 semantic_tokens_legend: None,
423 semantic_tokens_full: false,
424 semantic_tokens_full_delta: false,
425 semantic_tokens_range: false,
426 folding_ranges_supported: false,
427 })
428 .unwrap();
429
430 let messages = bridge.try_recv_all();
432 assert_eq!(messages.len(), 2);
433 }
434
435 #[test]
436 fn test_async_bridge_no_messages() {
437 let bridge = AsyncBridge::new();
438
439 let messages = bridge.try_recv_all();
441 assert_eq!(messages.len(), 0);
442 }
443
444 #[test]
445 fn test_async_bridge_clone_sender() {
446 let bridge = AsyncBridge::new();
447 let sender1 = bridge.sender();
448 let sender2 = sender1.clone();
449
450 sender1
452 .send(AsyncMessage::LspInitialized {
453 language: "rust".to_string(),
454 completion_trigger_characters: vec![],
455 semantic_tokens_legend: None,
456 semantic_tokens_full: false,
457 semantic_tokens_full_delta: false,
458 semantic_tokens_range: false,
459 folding_ranges_supported: false,
460 })
461 .unwrap();
462 sender2
463 .send(AsyncMessage::LspInitialized {
464 language: "typescript".to_string(),
465 completion_trigger_characters: vec![],
466 semantic_tokens_legend: None,
467 semantic_tokens_full: false,
468 semantic_tokens_full_delta: false,
469 semantic_tokens_range: false,
470 folding_ranges_supported: false,
471 })
472 .unwrap();
473
474 let messages = bridge.try_recv_all();
475 assert_eq!(messages.len(), 2);
476 }
477
478 #[test]
479 fn test_async_bridge_diagnostics() {
480 let bridge = AsyncBridge::new();
481 let sender = bridge.sender();
482
483 let diagnostics = vec![lsp_types::Diagnostic {
485 range: lsp_types::Range {
486 start: lsp_types::Position {
487 line: 0,
488 character: 0,
489 },
490 end: lsp_types::Position {
491 line: 0,
492 character: 5,
493 },
494 },
495 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
496 code: None,
497 code_description: None,
498 source: Some("rust-analyzer".to_string()),
499 message: "test error".to_string(),
500 related_information: None,
501 tags: None,
502 data: None,
503 }];
504
505 sender
506 .send(AsyncMessage::LspDiagnostics {
507 uri: "file:///test.rs".to_string(),
508 diagnostics: diagnostics.clone(),
509 })
510 .unwrap();
511
512 let messages = bridge.try_recv_all();
513 assert_eq!(messages.len(), 1);
514
515 match &messages[0] {
516 AsyncMessage::LspDiagnostics {
517 uri,
518 diagnostics: diags,
519 } => {
520 assert_eq!(uri, "file:///test.rs");
521 assert_eq!(diags.len(), 1);
522 assert_eq!(diags[0].message, "test error");
523 }
524 _ => panic!("Expected LspDiagnostics message"),
525 }
526 }
527
528 #[test]
529 fn test_async_bridge_error_message() {
530 let bridge = AsyncBridge::new();
531 let sender = bridge.sender();
532
533 sender
534 .send(AsyncMessage::LspError {
535 language: "rust".to_string(),
536 error: "Failed to initialize".to_string(),
537 stderr_log_path: None,
538 })
539 .unwrap();
540
541 let messages = bridge.try_recv_all();
542 assert_eq!(messages.len(), 1);
543
544 match &messages[0] {
545 AsyncMessage::LspError {
546 language,
547 error,
548 stderr_log_path,
549 } => {
550 assert_eq!(language, "rust");
551 assert_eq!(error, "Failed to initialize");
552 assert!(stderr_log_path.is_none());
553 }
554 _ => panic!("Expected LspError message"),
555 }
556 }
557
558 #[test]
559 fn test_async_bridge_clone_bridge() {
560 let bridge = AsyncBridge::new();
561 let bridge_clone = bridge.clone();
562 let sender = bridge.sender();
563
564 sender
566 .send(AsyncMessage::LspInitialized {
567 language: "rust".to_string(),
568 completion_trigger_characters: vec![],
569 semantic_tokens_legend: None,
570 semantic_tokens_full: false,
571 semantic_tokens_full_delta: false,
572 semantic_tokens_range: false,
573 folding_ranges_supported: false,
574 })
575 .unwrap();
576
577 let messages = bridge_clone.try_recv_all();
579 assert_eq!(messages.len(), 1);
580 }
581
582 #[test]
583 fn test_async_bridge_multiple_calls_to_try_recv_all() {
584 let bridge = AsyncBridge::new();
585 let sender = bridge.sender();
586
587 sender
588 .send(AsyncMessage::LspInitialized {
589 language: "rust".to_string(),
590 completion_trigger_characters: vec![],
591 semantic_tokens_legend: None,
592 semantic_tokens_full: false,
593 semantic_tokens_full_delta: false,
594 semantic_tokens_range: false,
595 folding_ranges_supported: false,
596 })
597 .unwrap();
598
599 let messages1 = bridge.try_recv_all();
601 assert_eq!(messages1.len(), 1);
602
603 let messages2 = bridge.try_recv_all();
605 assert_eq!(messages2.len(), 0);
606 }
607
608 #[test]
609 fn test_async_bridge_ordering() {
610 let bridge = AsyncBridge::new();
611 let sender = bridge.sender();
612
613 sender
615 .send(AsyncMessage::LspInitialized {
616 language: "rust".to_string(),
617 completion_trigger_characters: vec![],
618 semantic_tokens_legend: None,
619 semantic_tokens_full: false,
620 semantic_tokens_full_delta: false,
621 semantic_tokens_range: false,
622 folding_ranges_supported: false,
623 })
624 .unwrap();
625 sender
626 .send(AsyncMessage::LspInitialized {
627 language: "typescript".to_string(),
628 completion_trigger_characters: vec![],
629 semantic_tokens_legend: None,
630 semantic_tokens_full: false,
631 semantic_tokens_full_delta: false,
632 semantic_tokens_range: false,
633 folding_ranges_supported: false,
634 })
635 .unwrap();
636 sender
637 .send(AsyncMessage::LspInitialized {
638 language: "python".to_string(),
639 completion_trigger_characters: vec![],
640 semantic_tokens_legend: None,
641 semantic_tokens_full: false,
642 semantic_tokens_full_delta: false,
643 semantic_tokens_range: false,
644 folding_ranges_supported: false,
645 })
646 .unwrap();
647
648 let messages = bridge.try_recv_all();
650 assert_eq!(messages.len(), 3);
651
652 match (&messages[0], &messages[1], &messages[2]) {
653 (
654 AsyncMessage::LspInitialized { language: l1, .. },
655 AsyncMessage::LspInitialized { language: l2, .. },
656 AsyncMessage::LspInitialized { language: l3, .. },
657 ) => {
658 assert_eq!(l1, "rust");
659 assert_eq!(l2, "typescript");
660 assert_eq!(l3, "python");
661 }
662 _ => panic!("Expected ordered LspInitialized messages"),
663 }
664 }
665}