1use crate::services::terminal::TerminalId;
14use crate::view::file_tree::{FileTreeView, NodeId};
15use lsp_types::{
16 CodeActionOrCommand, CompletionItem, Diagnostic, FoldingRange, InlayHint, Location,
17 SemanticTokensFullDeltaResult, SemanticTokensRangeResult, SemanticTokensResult, SignatureHelp,
18};
19use serde_json::Value;
20use std::sync::mpsc;
21
22#[derive(Debug)]
24pub enum LspSemanticTokensResponse {
25 Full(Result<Option<SemanticTokensResult>, String>),
26 FullDelta(Result<Option<SemanticTokensFullDeltaResult>, String>),
27 Range(Result<Option<SemanticTokensRangeResult>, String>),
28}
29
30#[derive(Debug)]
32pub enum AsyncMessage {
33 LspDiagnostics {
35 uri: String,
36 diagnostics: Vec<Diagnostic>,
37 server_name: String,
39 },
40
41 LspInitialized {
43 language: String,
44 server_name: String,
46 capabilities: crate::services::lsp::manager::ServerCapabilitySummary,
48 },
49
50 LspError {
52 language: String,
53 error: String,
54 stderr_log_path: Option<std::path::PathBuf>,
56 },
57
58 LspCompletion {
60 request_id: u64,
61 items: Vec<CompletionItem>,
62 },
63
64 LspGotoDefinition {
66 request_id: u64,
67 locations: Vec<Location>,
68 },
69
70 LspRename {
72 request_id: u64,
73 result: Result<lsp_types::WorkspaceEdit, String>,
74 },
75
76 LspHover {
78 request_id: u64,
79 contents: String,
81 is_markdown: bool,
83 range: Option<((u32, u32), (u32, u32))>,
86 },
87
88 LspReferences {
90 request_id: u64,
91 locations: Vec<Location>,
92 },
93
94 LspSignatureHelp {
96 request_id: u64,
97 signature_help: Option<SignatureHelp>,
98 },
99
100 LspCodeActions {
102 request_id: u64,
103 actions: Vec<CodeActionOrCommand>,
104 },
105
106 LspCompletionResolved {
108 request_id: u64,
109 item: Result<lsp_types::CompletionItem, String>,
110 },
111
112 LspFormatting {
114 request_id: u64,
115 uri: String,
116 edits: Vec<lsp_types::TextEdit>,
117 },
118
119 LspPrepareRename {
121 request_id: u64,
122 result: Result<serde_json::Value, String>,
123 },
124
125 LspPulledDiagnostics {
127 request_id: u64,
128 uri: String,
129 result_id: Option<String>,
131 diagnostics: Vec<Diagnostic>,
133 unchanged: bool,
135 },
136
137 LspInlayHints {
139 request_id: u64,
140 uri: String,
141 hints: Vec<InlayHint>,
143 },
144
145 LspFoldingRanges {
147 request_id: u64,
148 uri: String,
149 ranges: Vec<FoldingRange>,
150 },
151
152 LspSemanticTokens {
154 request_id: u64,
155 uri: String,
156 response: LspSemanticTokensResponse,
157 },
158
159 LspServerQuiescent { language: String },
162
163 LspDiagnosticRefresh { language: String },
166
167 FileChanged { path: String },
169
170 GitStatusChanged { status: String },
172
173 FileExplorerInitialized(FileTreeView),
175
176 FileExplorerToggleNode(NodeId),
178
179 FileExplorerRefreshNode(NodeId),
181
182 FileExplorerExpandedToPath(FileTreeView),
185
186 Plugin(fresh_core::api::PluginAsyncMessage),
188
189 FileOpenDirectoryLoaded(std::io::Result<Vec<crate::services::fs::DirEntry>>),
191
192 FileOpenShortcutsLoaded(Vec<crate::app::file_open::NavigationShortcut>),
194
195 TerminalOutput { terminal_id: TerminalId },
197
198 PathChanged {
202 handle: u64,
205 path: std::path::PathBuf,
206 kind: PathChangeKind,
208 },
209
210 TerminalExited {
218 terminal_id: TerminalId,
219 exit_code: Option<i32>,
220 },
221
222 LspProgress {
224 language: String,
225 token: String,
226 value: LspProgressValue,
227 },
228
229 LspWindowMessage {
231 language: String,
232 message_type: LspMessageType,
233 message: String,
234 },
235
236 LspLogMessage {
238 language: String,
239 message_type: LspMessageType,
240 message: String,
241 },
242
243 LspApplyEdit {
246 edit: lsp_types::WorkspaceEdit,
247 label: Option<String>,
248 },
249
250 LspCodeActionResolved {
252 request_id: u64,
253 action: Result<lsp_types::CodeAction, String>,
254 },
255
256 LspServerRequest {
259 language: String,
260 server_command: String,
261 method: String,
262 params: Option<Value>,
263 },
264
265 PluginLspResponse {
267 language: String,
268 request_id: u64,
269 result: Result<Value, String>,
270 },
271
272 PluginProcessOutput {
274 process_id: u64,
276 stdout: String,
278 stderr: String,
280 exit_code: i32,
282 },
283
284 LspStatusUpdate {
286 language: String,
287 server_name: String,
289 status: LspServerStatus,
290 message: Option<String>,
291 },
292
293 GrammarRegistryBuilt {
297 registry: std::sync::Arc<crate::primitives::grammar::GrammarRegistry>,
298 callback_ids: Vec<fresh_core::api::JsCallbackId>,
299 },
300
301 QuickOpenFilesLoaded {
305 cwd: String,
309 files: std::sync::Arc<Vec<crate::input::quick_open::providers::FileEntry>>,
310 complete: bool,
311 },
312
313 PluginsDirLoaded {
317 dir: std::path::PathBuf,
318 errors: Vec<String>,
319 discovered_plugins: std::collections::HashMap<String, fresh_core::config::PluginConfig>,
320 },
321
322 PluginDeclarationsReady { declarations: Vec<(String, String)> },
326
327 PluginInitScriptLoaded(PluginInitScriptOutcome),
332}
333
334#[derive(Debug, Clone)]
337pub enum PluginInitScriptOutcome {
338 NotFound,
339 Disabled,
340 CrashFused { failures: u32 },
341 Loaded,
342 Failed { message: String },
343}
344
345#[derive(Debug, Clone, Copy)]
351pub enum PathChangeKind {
352 Modify,
353 Create,
354 Delete,
355 Rename,
356 Other,
357}
358
359impl PathChangeKind {
360 pub fn as_str(&self) -> &'static str {
361 match self {
362 PathChangeKind::Modify => "modify",
363 PathChangeKind::Create => "create",
364 PathChangeKind::Delete => "delete",
365 PathChangeKind::Rename => "rename",
366 PathChangeKind::Other => "other",
367 }
368 }
369}
370
371#[derive(Debug, Clone)]
373pub enum LspProgressValue {
374 Begin {
375 title: String,
376 message: Option<String>,
377 percentage: Option<u32>,
378 },
379 Report {
380 message: Option<String>,
381 percentage: Option<u32>,
382 },
383 End {
384 message: Option<String>,
385 },
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390pub enum LspMessageType {
391 Error = 1,
392 Warning = 2,
393 Info = 3,
394 Log = 4,
395}
396
397#[derive(Debug, Clone, Copy, PartialEq, Eq)]
399pub enum LspServerStatus {
400 Starting,
401 Initializing,
402 Running,
403 Error,
404 Shutdown,
405}
406
407#[derive(Clone)]
414pub struct AsyncBridge {
415 sender: mpsc::Sender<AsyncMessage>,
416 receiver: std::sync::Arc<std::sync::Mutex<mpsc::Receiver<AsyncMessage>>>,
418}
419
420impl AsyncBridge {
421 pub fn new() -> Self {
428 let (sender, receiver) = mpsc::channel();
429 Self {
430 sender,
431 receiver: std::sync::Arc::new(std::sync::Mutex::new(receiver)),
432 }
433 }
434
435 pub fn sender(&self) -> mpsc::Sender<AsyncMessage> {
442 self.sender.clone()
443 }
444
445 pub fn try_recv_all(&self) -> Vec<AsyncMessage> {
450 let mut messages = Vec::new();
451
452 if let Ok(receiver) = self.receiver.lock() {
454 while let Ok(msg) = receiver.try_recv() {
455 messages.push(msg);
456 }
457 }
458
459 messages
460 }
461
462 pub fn has_messages(&self) -> bool {
464 if let Ok(receiver) = self.receiver.lock() {
466 receiver.try_recv().is_ok()
467 } else {
468 false
469 }
470 }
471}
472
473impl Default for AsyncBridge {
474 fn default() -> Self {
475 Self::new()
476 }
477}
478
479#[cfg(test)]
480mod tests {
481 use super::*;
482
483 #[test]
484 fn test_async_bridge_send_receive() {
485 let bridge = AsyncBridge::new();
486 let sender = bridge.sender();
487
488 sender
490 .send(AsyncMessage::LspInitialized {
491 language: "rust".to_string(),
492 server_name: "test".to_string(),
493 capabilities: Default::default(),
494 })
495 .unwrap();
496
497 let messages = bridge.try_recv_all();
499 assert_eq!(messages.len(), 1);
500
501 match &messages[0] {
502 AsyncMessage::LspInitialized {
503 language,
504 server_name,
505 ..
506 } => {
507 assert_eq!(language, "rust");
508 assert_eq!(server_name, "test");
509 }
510 _ => panic!("Wrong message type"),
511 }
512 }
513
514 #[test]
515 fn test_async_bridge_multiple_messages() {
516 let bridge = AsyncBridge::new();
517 let sender = bridge.sender();
518
519 sender
521 .send(AsyncMessage::LspInitialized {
522 language: "rust".to_string(),
523 server_name: "test".to_string(),
524 capabilities: Default::default(),
525 })
526 .unwrap();
527 sender
528 .send(AsyncMessage::LspInitialized {
529 language: "typescript".to_string(),
530 server_name: "test".to_string(),
531 capabilities: Default::default(),
532 })
533 .unwrap();
534
535 let messages = bridge.try_recv_all();
537 assert_eq!(messages.len(), 2);
538 }
539
540 #[test]
541 fn test_async_bridge_no_messages() {
542 let bridge = AsyncBridge::new();
543
544 let messages = bridge.try_recv_all();
546 assert_eq!(messages.len(), 0);
547 }
548
549 #[test]
550 fn test_async_bridge_clone_sender() {
551 let bridge = AsyncBridge::new();
552 let sender1 = bridge.sender();
553 let sender2 = sender1.clone();
554
555 sender1
557 .send(AsyncMessage::LspInitialized {
558 language: "rust".to_string(),
559 server_name: "test".to_string(),
560 capabilities: Default::default(),
561 })
562 .unwrap();
563 sender2
564 .send(AsyncMessage::LspInitialized {
565 language: "typescript".to_string(),
566 server_name: "test".to_string(),
567 capabilities: Default::default(),
568 })
569 .unwrap();
570
571 let messages = bridge.try_recv_all();
572 assert_eq!(messages.len(), 2);
573 }
574
575 #[test]
576 fn test_async_bridge_diagnostics() {
577 let bridge = AsyncBridge::new();
578 let sender = bridge.sender();
579
580 let diagnostics = vec![lsp_types::Diagnostic {
582 range: lsp_types::Range {
583 start: lsp_types::Position {
584 line: 0,
585 character: 0,
586 },
587 end: lsp_types::Position {
588 line: 0,
589 character: 5,
590 },
591 },
592 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
593 code: None,
594 code_description: None,
595 source: Some("rust-analyzer".to_string()),
596 message: "test error".to_string(),
597 related_information: None,
598 tags: None,
599 data: None,
600 }];
601
602 sender
603 .send(AsyncMessage::LspDiagnostics {
604 uri: "file:///test.rs".to_string(),
605 diagnostics: diagnostics.clone(),
606 server_name: "rust-analyzer".to_string(),
607 })
608 .unwrap();
609
610 let messages = bridge.try_recv_all();
611 assert_eq!(messages.len(), 1);
612
613 match &messages[0] {
614 AsyncMessage::LspDiagnostics {
615 uri,
616 diagnostics: diags,
617 server_name,
618 } => {
619 assert_eq!(uri, "file:///test.rs");
620 assert_eq!(diags.len(), 1);
621 assert_eq!(diags[0].message, "test error");
622 assert_eq!(server_name, "rust-analyzer");
623 }
624 _ => panic!("Expected LspDiagnostics message"),
625 }
626 }
627
628 #[test]
629 fn test_async_bridge_error_message() {
630 let bridge = AsyncBridge::new();
631 let sender = bridge.sender();
632
633 sender
634 .send(AsyncMessage::LspError {
635 language: "rust".to_string(),
636 error: "Failed to initialize".to_string(),
637 stderr_log_path: None,
638 })
639 .unwrap();
640
641 let messages = bridge.try_recv_all();
642 assert_eq!(messages.len(), 1);
643
644 match &messages[0] {
645 AsyncMessage::LspError {
646 language,
647 error,
648 stderr_log_path,
649 } => {
650 assert_eq!(language, "rust");
651 assert_eq!(error, "Failed to initialize");
652 assert!(stderr_log_path.is_none());
653 }
654 _ => panic!("Expected LspError message"),
655 }
656 }
657
658 #[test]
659 fn test_async_bridge_clone_bridge() {
660 let bridge = AsyncBridge::new();
661 let bridge_clone = bridge.clone();
662 let sender = bridge.sender();
663
664 sender
666 .send(AsyncMessage::LspInitialized {
667 language: "rust".to_string(),
668 server_name: "test".to_string(),
669 capabilities: Default::default(),
670 })
671 .unwrap();
672
673 let messages = bridge_clone.try_recv_all();
675 assert_eq!(messages.len(), 1);
676 }
677
678 #[test]
679 fn test_async_bridge_multiple_calls_to_try_recv_all() {
680 let bridge = AsyncBridge::new();
681 let sender = bridge.sender();
682
683 sender
684 .send(AsyncMessage::LspInitialized {
685 language: "rust".to_string(),
686 server_name: "test".to_string(),
687 capabilities: Default::default(),
688 })
689 .unwrap();
690
691 let messages1 = bridge.try_recv_all();
693 assert_eq!(messages1.len(), 1);
694
695 let messages2 = bridge.try_recv_all();
697 assert_eq!(messages2.len(), 0);
698 }
699
700 #[test]
701 fn test_async_bridge_ordering() {
702 let bridge = AsyncBridge::new();
703 let sender = bridge.sender();
704
705 sender
707 .send(AsyncMessage::LspInitialized {
708 language: "rust".to_string(),
709 server_name: "test".to_string(),
710 capabilities: Default::default(),
711 })
712 .unwrap();
713 sender
714 .send(AsyncMessage::LspInitialized {
715 language: "typescript".to_string(),
716 server_name: "test".to_string(),
717 capabilities: Default::default(),
718 })
719 .unwrap();
720 sender
721 .send(AsyncMessage::LspInitialized {
722 language: "python".to_string(),
723 server_name: "test".to_string(),
724 capabilities: Default::default(),
725 })
726 .unwrap();
727
728 let messages = bridge.try_recv_all();
730 assert_eq!(messages.len(), 3);
731
732 match (&messages[0], &messages[1], &messages[2]) {
733 (
734 AsyncMessage::LspInitialized { language: l1, .. },
735 AsyncMessage::LspInitialized { language: l2, .. },
736 AsyncMessage::LspInitialized { language: l3, .. },
737 ) => {
738 assert_eq!(l1, "rust");
739 assert_eq!(l2, "typescript");
740 assert_eq!(l3, "python");
741 }
742 _ => panic!("Expected ordered LspInitialized messages"),
743 }
744 }
745}