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 files: std::sync::Arc<Vec<crate::input::quick_open::providers::FileEntry>>,
306 complete: bool,
307 },
308
309 PluginsDirLoaded {
313 dir: std::path::PathBuf,
314 errors: Vec<String>,
315 discovered_plugins: std::collections::HashMap<String, fresh_core::config::PluginConfig>,
316 },
317
318 PluginDeclarationsReady { declarations: Vec<(String, String)> },
322
323 PluginInitScriptLoaded(PluginInitScriptOutcome),
328}
329
330#[derive(Debug, Clone)]
333pub enum PluginInitScriptOutcome {
334 NotFound,
335 Disabled,
336 CrashFused { failures: u32 },
337 Loaded,
338 Failed { message: String },
339}
340
341#[derive(Debug, Clone, Copy)]
347pub enum PathChangeKind {
348 Modify,
349 Create,
350 Delete,
351 Rename,
352 Other,
353}
354
355impl PathChangeKind {
356 pub fn as_str(&self) -> &'static str {
357 match self {
358 PathChangeKind::Modify => "modify",
359 PathChangeKind::Create => "create",
360 PathChangeKind::Delete => "delete",
361 PathChangeKind::Rename => "rename",
362 PathChangeKind::Other => "other",
363 }
364 }
365}
366
367#[derive(Debug, Clone)]
369pub enum LspProgressValue {
370 Begin {
371 title: String,
372 message: Option<String>,
373 percentage: Option<u32>,
374 },
375 Report {
376 message: Option<String>,
377 percentage: Option<u32>,
378 },
379 End {
380 message: Option<String>,
381 },
382}
383
384#[derive(Debug, Clone, Copy, PartialEq, Eq)]
386pub enum LspMessageType {
387 Error = 1,
388 Warning = 2,
389 Info = 3,
390 Log = 4,
391}
392
393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
395pub enum LspServerStatus {
396 Starting,
397 Initializing,
398 Running,
399 Error,
400 Shutdown,
401}
402
403#[derive(Clone)]
410pub struct AsyncBridge {
411 sender: mpsc::Sender<AsyncMessage>,
412 receiver: std::sync::Arc<std::sync::Mutex<mpsc::Receiver<AsyncMessage>>>,
414}
415
416impl AsyncBridge {
417 pub fn new() -> Self {
424 let (sender, receiver) = mpsc::channel();
425 Self {
426 sender,
427 receiver: std::sync::Arc::new(std::sync::Mutex::new(receiver)),
428 }
429 }
430
431 pub fn sender(&self) -> mpsc::Sender<AsyncMessage> {
438 self.sender.clone()
439 }
440
441 pub fn try_recv_all(&self) -> Vec<AsyncMessage> {
446 let mut messages = Vec::new();
447
448 if let Ok(receiver) = self.receiver.lock() {
450 while let Ok(msg) = receiver.try_recv() {
451 messages.push(msg);
452 }
453 }
454
455 messages
456 }
457
458 pub fn has_messages(&self) -> bool {
460 if let Ok(receiver) = self.receiver.lock() {
462 receiver.try_recv().is_ok()
463 } else {
464 false
465 }
466 }
467}
468
469impl Default for AsyncBridge {
470 fn default() -> Self {
471 Self::new()
472 }
473}
474
475#[cfg(test)]
476mod tests {
477 use super::*;
478
479 #[test]
480 fn test_async_bridge_send_receive() {
481 let bridge = AsyncBridge::new();
482 let sender = bridge.sender();
483
484 sender
486 .send(AsyncMessage::LspInitialized {
487 language: "rust".to_string(),
488 server_name: "test".to_string(),
489 capabilities: Default::default(),
490 })
491 .unwrap();
492
493 let messages = bridge.try_recv_all();
495 assert_eq!(messages.len(), 1);
496
497 match &messages[0] {
498 AsyncMessage::LspInitialized {
499 language,
500 server_name,
501 ..
502 } => {
503 assert_eq!(language, "rust");
504 assert_eq!(server_name, "test");
505 }
506 _ => panic!("Wrong message type"),
507 }
508 }
509
510 #[test]
511 fn test_async_bridge_multiple_messages() {
512 let bridge = AsyncBridge::new();
513 let sender = bridge.sender();
514
515 sender
517 .send(AsyncMessage::LspInitialized {
518 language: "rust".to_string(),
519 server_name: "test".to_string(),
520 capabilities: Default::default(),
521 })
522 .unwrap();
523 sender
524 .send(AsyncMessage::LspInitialized {
525 language: "typescript".to_string(),
526 server_name: "test".to_string(),
527 capabilities: Default::default(),
528 })
529 .unwrap();
530
531 let messages = bridge.try_recv_all();
533 assert_eq!(messages.len(), 2);
534 }
535
536 #[test]
537 fn test_async_bridge_no_messages() {
538 let bridge = AsyncBridge::new();
539
540 let messages = bridge.try_recv_all();
542 assert_eq!(messages.len(), 0);
543 }
544
545 #[test]
546 fn test_async_bridge_clone_sender() {
547 let bridge = AsyncBridge::new();
548 let sender1 = bridge.sender();
549 let sender2 = sender1.clone();
550
551 sender1
553 .send(AsyncMessage::LspInitialized {
554 language: "rust".to_string(),
555 server_name: "test".to_string(),
556 capabilities: Default::default(),
557 })
558 .unwrap();
559 sender2
560 .send(AsyncMessage::LspInitialized {
561 language: "typescript".to_string(),
562 server_name: "test".to_string(),
563 capabilities: Default::default(),
564 })
565 .unwrap();
566
567 let messages = bridge.try_recv_all();
568 assert_eq!(messages.len(), 2);
569 }
570
571 #[test]
572 fn test_async_bridge_diagnostics() {
573 let bridge = AsyncBridge::new();
574 let sender = bridge.sender();
575
576 let diagnostics = vec![lsp_types::Diagnostic {
578 range: lsp_types::Range {
579 start: lsp_types::Position {
580 line: 0,
581 character: 0,
582 },
583 end: lsp_types::Position {
584 line: 0,
585 character: 5,
586 },
587 },
588 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
589 code: None,
590 code_description: None,
591 source: Some("rust-analyzer".to_string()),
592 message: "test error".to_string(),
593 related_information: None,
594 tags: None,
595 data: None,
596 }];
597
598 sender
599 .send(AsyncMessage::LspDiagnostics {
600 uri: "file:///test.rs".to_string(),
601 diagnostics: diagnostics.clone(),
602 server_name: "rust-analyzer".to_string(),
603 })
604 .unwrap();
605
606 let messages = bridge.try_recv_all();
607 assert_eq!(messages.len(), 1);
608
609 match &messages[0] {
610 AsyncMessage::LspDiagnostics {
611 uri,
612 diagnostics: diags,
613 server_name,
614 } => {
615 assert_eq!(uri, "file:///test.rs");
616 assert_eq!(diags.len(), 1);
617 assert_eq!(diags[0].message, "test error");
618 assert_eq!(server_name, "rust-analyzer");
619 }
620 _ => panic!("Expected LspDiagnostics message"),
621 }
622 }
623
624 #[test]
625 fn test_async_bridge_error_message() {
626 let bridge = AsyncBridge::new();
627 let sender = bridge.sender();
628
629 sender
630 .send(AsyncMessage::LspError {
631 language: "rust".to_string(),
632 error: "Failed to initialize".to_string(),
633 stderr_log_path: None,
634 })
635 .unwrap();
636
637 let messages = bridge.try_recv_all();
638 assert_eq!(messages.len(), 1);
639
640 match &messages[0] {
641 AsyncMessage::LspError {
642 language,
643 error,
644 stderr_log_path,
645 } => {
646 assert_eq!(language, "rust");
647 assert_eq!(error, "Failed to initialize");
648 assert!(stderr_log_path.is_none());
649 }
650 _ => panic!("Expected LspError message"),
651 }
652 }
653
654 #[test]
655 fn test_async_bridge_clone_bridge() {
656 let bridge = AsyncBridge::new();
657 let bridge_clone = bridge.clone();
658 let sender = bridge.sender();
659
660 sender
662 .send(AsyncMessage::LspInitialized {
663 language: "rust".to_string(),
664 server_name: "test".to_string(),
665 capabilities: Default::default(),
666 })
667 .unwrap();
668
669 let messages = bridge_clone.try_recv_all();
671 assert_eq!(messages.len(), 1);
672 }
673
674 #[test]
675 fn test_async_bridge_multiple_calls_to_try_recv_all() {
676 let bridge = AsyncBridge::new();
677 let sender = bridge.sender();
678
679 sender
680 .send(AsyncMessage::LspInitialized {
681 language: "rust".to_string(),
682 server_name: "test".to_string(),
683 capabilities: Default::default(),
684 })
685 .unwrap();
686
687 let messages1 = bridge.try_recv_all();
689 assert_eq!(messages1.len(), 1);
690
691 let messages2 = bridge.try_recv_all();
693 assert_eq!(messages2.len(), 0);
694 }
695
696 #[test]
697 fn test_async_bridge_ordering() {
698 let bridge = AsyncBridge::new();
699 let sender = bridge.sender();
700
701 sender
703 .send(AsyncMessage::LspInitialized {
704 language: "rust".to_string(),
705 server_name: "test".to_string(),
706 capabilities: Default::default(),
707 })
708 .unwrap();
709 sender
710 .send(AsyncMessage::LspInitialized {
711 language: "typescript".to_string(),
712 server_name: "test".to_string(),
713 capabilities: Default::default(),
714 })
715 .unwrap();
716 sender
717 .send(AsyncMessage::LspInitialized {
718 language: "python".to_string(),
719 server_name: "test".to_string(),
720 capabilities: Default::default(),
721 })
722 .unwrap();
723
724 let messages = bridge.try_recv_all();
726 assert_eq!(messages.len(), 3);
727
728 match (&messages[0], &messages[1], &messages[2]) {
729 (
730 AsyncMessage::LspInitialized { language: l1, .. },
731 AsyncMessage::LspInitialized { language: l2, .. },
732 AsyncMessage::LspInitialized { language: l3, .. },
733 ) => {
734 assert_eq!(l1, "rust");
735 assert_eq!(l2, "typescript");
736 assert_eq!(l3, "python");
737 }
738 _ => panic!("Expected ordered LspInitialized messages"),
739 }
740 }
741}