1#![feature(new_range_api)]
2#![warn(missing_docs)]
3use dashmap::{DashMap, DashSet};
4use nargo_ir::{ElementIR, ExpressionIR, JsExpr, JsProgram, JsStmt, TemplateNodeIR};
5use nargo_parser::{template::VueTemplateParser, ParseState, ScriptParser, TemplateParser};
6use nargo_types::{is_pos_in_span, Position as NargoPosition, Span as NargoSpan};
7
8use futures::Future;
9use nargo_lsp::types::NargoLanguage;
10use oak_core::{
11 parser::session::ParseSession,
12 tree::{GreenNode, RedNode},
13 Range,
14};
15use oak_lsp::{service::LanguageService, types::SourcePosition as OakPosition};
16use oak_vfs::{MemoryVfs, Vfs};
17
18use serde::{Deserialize, Serialize};
19use std::sync::Arc;
20use tokio::sync::mpsc as tokio_mpsc;
21use url::Url;
22
23type ModuleId = String;
25
26type MessageId = String;
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
31pub enum MessageType {
32 Message,
34 Event,
36 Request,
38 Response,
40 Broadcast,
42 Multicast,
44 Error,
46 Ping,
48 Pong,
50 Subscribe,
52 Unsubscribe,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
58pub enum EventType {
59 ModuleInitialized,
61 ModuleStateUpdated,
63 CompileStarted,
65 CompileCompleted,
67 CompileError,
69 FileChanged,
71 ConfigurationChanged,
73 ModuleRegistered,
75 ModuleUnregistered,
77 MessageSent,
79 MessageReceived,
81 HeartbeatTimeout,
83 NetworkDisconnected,
85 NetworkConnected,
87 ResourceLoadStarted,
89 ResourceLoadCompleted,
91 ResourceLoadError,
93 PerformanceWarning,
95 SystemWarning,
97 SystemError,
99}
100
101impl EventType {
102 pub fn to_string(&self) -> String {
104 match self {
105 EventType::ModuleInitialized => "ModuleInitialized",
106 EventType::ModuleStateUpdated => "ModuleStateUpdated",
107 EventType::CompileStarted => "CompileStarted",
108 EventType::CompileCompleted => "CompileCompleted",
109 EventType::CompileError => "CompileError",
110 EventType::FileChanged => "FileChanged",
111 EventType::ConfigurationChanged => "ConfigurationChanged",
112 EventType::ModuleRegistered => "ModuleRegistered",
113 EventType::ModuleUnregistered => "ModuleUnregistered",
114 EventType::MessageSent => "MessageSent",
115 EventType::MessageReceived => "MessageReceived",
116 EventType::HeartbeatTimeout => "HeartbeatTimeout",
117 EventType::NetworkDisconnected => "NetworkDisconnected",
118 EventType::NetworkConnected => "NetworkConnected",
119 EventType::ResourceLoadStarted => "ResourceLoadStarted",
120 EventType::ResourceLoadCompleted => "ResourceLoadCompleted",
121 EventType::ResourceLoadError => "ResourceLoadError",
122 EventType::PerformanceWarning => "PerformanceWarning",
123 EventType::SystemWarning => "SystemWarning",
124 EventType::SystemError => "SystemError",
125 }
126 .to_string()
127 }
128}
129
130#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
132pub enum MessageStatus {
133 Created,
135 Sent,
137 Received,
139 Acknowledged,
141 Timeout,
143 Failed,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct Message {
150 pub id: MessageId,
152 pub message_type: MessageType,
154 pub sender: ModuleId,
156 pub receiver: ModuleId,
158 pub event_type: Option<EventType>,
160 pub data: serde_json::Value,
162 pub timestamp: u64,
164 pub status: MessageStatus,
166 pub timeout: Option<u64>,
168 pub retry_count: u8,
170}
171
172impl Message {
173 pub fn new_message(sender: ModuleId, receiver: ModuleId, data: serde_json::Value) -> Self {
175 Self { id: format!("msg-{}", chrono::Utc::now().timestamp_millis()), message_type: MessageType::Message, sender, receiver, event_type: None, data, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(5000), retry_count: 0 }
176 }
177
178 pub fn new_event(sender: ModuleId, event_type: EventType, data: serde_json::Value) -> Self {
180 Self { id: format!("event-{}-{}", event_type.to_string(), chrono::Utc::now().timestamp_millis()), message_type: MessageType::Event, sender, receiver: "broadcast".to_string(), event_type: Some(event_type), data, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(3000), retry_count: 0 }
181 }
182
183 pub fn new_request(sender: ModuleId, receiver: ModuleId, data: serde_json::Value) -> Self {
185 Self { id: format!("req-{}", chrono::Utc::now().timestamp_millis()), message_type: MessageType::Request, sender, receiver, event_type: None, data, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(10000), retry_count: 3 }
186 }
187
188 pub fn new_response(request_id: MessageId, sender: ModuleId, receiver: ModuleId, data: serde_json::Value) -> Self {
190 Self { id: format!("resp-{}", request_id), message_type: MessageType::Response, sender, receiver, event_type: None, data, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(5000), retry_count: 0 }
191 }
192
193 pub fn new_broadcast(sender: ModuleId, data: serde_json::Value) -> Self {
195 Self { id: format!("broadcast-{}", chrono::Utc::now().timestamp_millis()), message_type: MessageType::Broadcast, sender, receiver: "broadcast".to_string(), event_type: None, data, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(3000), retry_count: 0 }
196 }
197
198 pub fn new_ping(sender: ModuleId, receiver: ModuleId) -> Self {
200 Self { id: format!("ping-{}", chrono::Utc::now().timestamp_millis()), message_type: MessageType::Ping, sender, receiver, event_type: None, data: serde_json::Value::Null, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(2000), retry_count: 1 }
201 }
202
203 pub fn new_pong(sender: ModuleId, receiver: ModuleId, ping_id: MessageId) -> Self {
205 Self { id: format!("pong-{}", ping_id), message_type: MessageType::Pong, sender, receiver, event_type: None, data: serde_json::json!({ "ping_id": ping_id }), timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(1000), retry_count: 0 }
206 }
207
208 pub fn new_subscribe(sender: ModuleId, event_type: EventType) -> Self {
210 Self { id: format!("subscribe-{}-{}", event_type.to_string(), chrono::Utc::now().timestamp_millis()), message_type: MessageType::Subscribe, sender, receiver: "system".to_string(), event_type: Some(event_type), data: serde_json::Value::Null, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(3000), retry_count: 0 }
211 }
212
213 pub fn new_unsubscribe(sender: ModuleId, event_type: EventType) -> Self {
215 Self { id: format!("unsubscribe-{}-{}", event_type.to_string(), chrono::Utc::now().timestamp_millis()), message_type: MessageType::Unsubscribe, sender, receiver: "system".to_string(), event_type: Some(event_type), data: serde_json::Value::Null, timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(3000), retry_count: 0 }
216 }
217
218 pub fn new_error(sender: ModuleId, receiver: ModuleId, error: String) -> Self {
220 Self { id: format!("error-{}", chrono::Utc::now().timestamp_millis()), message_type: MessageType::Error, sender, receiver, event_type: None, data: serde_json::json!({ "error": error }), timestamp: chrono::Utc::now().timestamp_millis() as u64, status: MessageStatus::Created, timeout: Some(3000), retry_count: 0 }
221 }
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226pub struct MessageBatch {
227 pub batch_id: String,
229 pub messages: Vec<Message>,
231 pub size: usize,
233 pub timestamp: u64,
235}
236
237#[derive(Clone)]
239pub struct ModuleCommunicationManager {
240 modules: DashMap<ModuleId, ModuleInfo>,
242 message_channels: DashMap<ModuleId, tokio_mpsc::Sender<Message>>,
244 event_listeners: DashMap<EventType, Vec<ModuleId>>,
246 message_status: DashMap<MessageId, Message>,
248 heartbeat_status: DashMap<ModuleId, u64>,
250 heartbeat_interval: u64,
252 message_buffers: DashMap<ModuleId, Vec<Message>>,
254 batch_size_threshold: usize,
256 batch_interval: u64,
258 message_cache: DashMap<String, serde_json::Value>,
260 cache_size_limit: usize,
262}
263
264unsafe impl Send for ModuleCommunicationManager {}
266unsafe impl Sync for ModuleCommunicationManager {}
267
268#[derive(Clone)]
270struct ModuleInfo {
271 name: String,
273 version: String,
275 status: ModuleStatus,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
281enum ModuleStatus {
282 Uninitialized,
284 Initializing,
286 Initialized,
288 Running,
290 Error,
292}
293
294impl ModuleCommunicationManager {
295 pub fn new() -> Self {
297 Self {
298 modules: DashMap::new(),
299 message_channels: DashMap::new(),
300 event_listeners: DashMap::new(),
301 message_status: DashMap::new(),
302
303 heartbeat_status: DashMap::new(),
304 heartbeat_interval: 30000, message_buffers: DashMap::new(),
306 batch_size_threshold: 10, batch_interval: 100, message_cache: DashMap::new(),
309 cache_size_limit: 1000, }
311 }
312
313 pub fn register_module(&self, module_id: ModuleId, name: String, version: String) -> tokio_mpsc::Receiver<Message> {
315 let (tx, rx) = tokio_mpsc::channel(100);
316
317 let name_clone = name.clone();
318 let version_clone = version.clone();
319
320 self.modules.insert(module_id.clone(), ModuleInfo { name, version, status: ModuleStatus::Uninitialized });
321
322 self.message_channels.insert(module_id.clone(), tx);
323
324 self.heartbeat_status.insert(module_id.clone(), chrono::Utc::now().timestamp_millis() as u64);
326
327 let event_data = serde_json::json!({
329 "module_id": module_id,
330 "name": name_clone,
331 "version": version_clone
332 });
333 let manager = self.clone();
334 tokio::spawn(async move {
335 manager.broadcast_event(EventType::ModuleRegistered, event_data, "system".to_string()).await;
336 });
337
338 rx
339 }
340
341 pub async fn send_message(&self, mut message: Message) {
343 self.message_status.insert(message.id.clone(), message.clone());
345
346 message.status = MessageStatus::Sent;
348 self.message_status.insert(message.id.clone(), message.clone());
349
350 let receiver = message.receiver.clone();
351
352 let tx = self.message_channels.get(&receiver).map(|r| r.value().clone());
354
355 if let Some(tx) = tx {
356 if tx.send(message.clone()).await.is_err() {
357 self.message_channels.remove(&receiver);
359 self.modules.remove(&receiver);
360 self.heartbeat_status.remove(&receiver);
361
362 let mut failed_msg = message;
364 failed_msg.status = MessageStatus::Failed;
365 self.message_status.insert(failed_msg.id.clone(), failed_msg);
366
367 let event_data = serde_json::json!({
369 "module_id": receiver
370 });
371 let event_message = Message::new_event("system".to_string(), EventType::ModuleUnregistered, event_data);
373 self.queue_message(event_message);
374 }
375 else {
376 let event_data = serde_json::json!({
378 "message_id": message.id,
379 "sender": message.sender,
380 "receiver": message.receiver
381 });
382 let event_message = Message::new_event("system".to_string(), EventType::MessageSent, event_data);
384 self.queue_message(event_message);
385
386 }
416 }
417 else {
418 message.status = MessageStatus::Failed;
420 self.message_status.insert(message.id.clone(), message);
421 }
422 }
423
424 pub async fn broadcast_event(&self, event_type: EventType, data: serde_json::Value, sender: ModuleId) {
426 let message = Message::new_event(sender, event_type.clone(), data);
427
428 let listeners = {
430 self.event_listeners.get(&event_type).map(|l| l.value().clone())
432 };
433
434 if let Some(listeners) = listeners {
435 for module_id in listeners {
436 let mut msg = message.clone();
437 msg.receiver = module_id;
438 self.send_message(msg).await;
440 }
441 }
442 }
443
444 pub fn register_event_listener(&self, event_type: EventType, module_id: ModuleId) {
446 self.event_listeners
447 .entry(event_type)
448 .and_modify(|listeners| {
449 if !listeners.contains(&module_id) {
450 listeners.push(module_id.clone());
451 }
452 })
453 .or_insert_with(|| vec![module_id]);
454 }
455
456 pub fn remove_event_listener(&self, event_type: EventType, module_id: &ModuleId) {
458 if let Some(mut listeners) = self.event_listeners.get_mut(&event_type) {
459 listeners.retain(|id| id != module_id);
460 }
461 }
462
463 pub fn update_module_status(&self, module_id: &ModuleId, status: ModuleStatus) {
465 if let Some(mut module) = self.modules.get_mut(module_id) {
466 module.status = status;
467 }
468 }
469
470 pub fn get_module_status(&self, module_id: &ModuleId) -> Option<ModuleStatus> {
472 self.modules.get(module_id).map(|module| module.status.clone())
473 }
474
475 pub fn list_modules(&self) -> Vec<(ModuleId, ModuleInfo)> {
477 self.modules.iter().map(|entry| (entry.key().clone(), entry.value().clone())).collect()
478 }
479
480 pub async fn acknowledge_message(&self, message_id: &MessageId) {
482 if let Some(mut msg) = self.message_status.get_mut(message_id) {
483 msg.status = MessageStatus::Acknowledged;
484
485 let event_data = serde_json::json!({
487 "message_id": message_id
488 });
489 self.broadcast_event(EventType::MessageReceived, event_data, "system".to_string()).await;
490
491 }
494 }
495
496 pub async fn send_message_with_ack(&self, message: Message) -> Result<Message, String> {
498 let timeout = message.timeout.unwrap_or(5000);
499
500 self.send_message(message).await;
501
502 Err("Message acknowledgement not supported".to_string())
504 }
505
506 pub async fn start_heartbeat(&self) {
508 let manager = self.clone();
509 tokio::spawn(async move {
510 loop {
511 tokio::time::sleep(tokio::time::Duration::from_millis(manager.heartbeat_interval)).await;
512 manager.check_heartbeats().await;
513 }
514 });
515 }
516
517 async fn check_heartbeats(&self) {
519 let current_time = chrono::Utc::now().timestamp_millis() as u64;
520 let mut modules_to_remove = Vec::new();
521
522 for entry in self.heartbeat_status.iter() {
523 let module_id = entry.key();
524 let last_heartbeat = entry.value();
525
526 if current_time - *last_heartbeat > self.heartbeat_interval * 2 {
527 modules_to_remove.push(module_id.clone());
528 }
529 }
530
531 for module_id in modules_to_remove {
532 self.message_channels.remove(&module_id);
534 self.modules.remove(&module_id);
535 self.heartbeat_status.remove(&module_id);
536
537 let event_data = serde_json::json!({
539 "module_id": module_id
540 });
541 self.broadcast_event(EventType::HeartbeatTimeout, event_data, "system".to_string()).await;
542 }
543 }
544
545 pub async fn send_ping(&self, module_id: &ModuleId) {
547 let ping_message = Message::new_ping("system".to_string(), module_id.clone());
548 self.send_message(ping_message).await;
549 }
550
551 pub async fn handle_pong(&self, module_id: &ModuleId) {
553 self.heartbeat_status.insert(module_id.clone(), chrono::Utc::now().timestamp_millis() as u64);
555 }
556
557 pub fn get_message_status(&self, message_id: &MessageId) -> Option<MessageStatus> {
559 self.message_status.get(message_id).map(|msg| msg.status.clone())
560 }
561
562 pub fn cleanup_expired_messages(&self) {
564 let current_time = chrono::Utc::now().timestamp_millis() as u64;
565 let mut messages_to_remove = Vec::new();
566
567 for entry in self.message_status.iter() {
568 let message_id = entry.key();
569 let message = entry.value();
570
571 if message.status == MessageStatus::Acknowledged || (message.status == MessageStatus::Timeout && current_time - message.timestamp > 300000) {
573 messages_to_remove.push(message_id.clone());
574 }
575 }
576
577 for message_id in messages_to_remove {
578 self.message_status.remove(&message_id);
579 }
580 }
581
582 pub async fn send_message_batch(&self, batch: MessageBatch) {
584 for message in batch.messages {
585 self.send_message(message).await;
586 }
587 }
588
589 pub fn queue_message(&self, message: Message) {
591 self.message_buffers
592 .entry(message.receiver.clone())
593 .and_modify(|buffer| {
594 buffer.push(message.clone());
595 if buffer.len() >= self.batch_size_threshold {
597 let batch = MessageBatch { batch_id: format!("batch-{}", chrono::Utc::now().timestamp_millis()), messages: buffer.drain(..).collect(), size: buffer.len(), timestamp: chrono::Utc::now().timestamp_millis() as u64 };
598 let manager = self.clone();
599 tokio::spawn(async move {
600 manager.send_message_batch(batch).await;
601 });
602 }
603 })
604 .or_insert_with(|| vec![message]);
605 }
606
607 pub async fn process_message_batches(&self) {
609 for mut entry in self.message_buffers.iter_mut() {
610 let module_id = entry.key().clone();
611 let buffer = entry.value_mut();
612
613 if !buffer.is_empty() {
614 let batch = MessageBatch { batch_id: format!("batch-{}-{}", module_id, chrono::Utc::now().timestamp_millis()), messages: buffer.drain(..).collect(), size: buffer.len(), timestamp: chrono::Utc::now().timestamp_millis() as u64 };
615 self.send_message_batch(batch).await;
616 }
617 }
618 }
619
620 pub async fn start_batch_processor(&self) {
622 let manager = self.clone();
623 tokio::spawn(async move {
624 loop {
625 tokio::time::sleep(tokio::time::Duration::from_millis(manager.batch_interval)).await;
626 manager.process_message_batches().await;
627 }
628 });
629 }
630
631 pub fn cache_message(&self, key: String, value: serde_json::Value) {
633 if self.message_cache.len() >= self.cache_size_limit {
635 if let Some(first_key) = self.message_cache.iter().next() {
637 self.message_cache.remove(first_key.key());
638 }
639 }
640 self.message_cache.insert(key, value);
641 }
642
643 pub fn get_cached_message(&self, key: &str) -> Option<serde_json::Value> {
645 self.message_cache.get(key).and_then(|v| Some(v.clone()))
646 }
647
648 pub fn clear_cache(&self) {
650 self.message_cache.clear();
651 }
652
653 pub fn set_batch_size_threshold(&mut self, threshold: usize) {
655 self.batch_size_threshold = threshold;
656 }
657
658 pub fn set_batch_interval(&mut self, interval: u64) {
660 self.batch_interval = interval;
661 }
662
663 pub fn set_cache_size_limit(&mut self, limit: usize) {
665 self.cache_size_limit = limit;
666 }
667}
668
669#[derive(Debug, Clone, PartialEq, Eq)]
670enum CompletionContext {
671 Tag,
672 Attribute(String),
673 Expression,
674}
675
676pub struct NargoLanguageService {
677 vfs: MemoryVfs,
678 auto_imports: DashSet<String>,
679 workspace: oak_lsp::workspace::WorkspaceManager,
680 sessions: DashMap<String, ParseSession<NargoLanguage>>,
681}
682
683impl NargoLanguageService {
684 pub fn new() -> Self {
685 let auto_imports = DashSet::new();
686 auto_imports.insert("signal".to_string());
688 auto_imports.insert("computed".to_string());
689 auto_imports.insert("on_mount".to_string());
690 auto_imports.insert("on_unmount".to_string());
691 auto_imports.insert("on_cleanup".to_string());
692
693 Self { vfs: MemoryVfs::new(), auto_imports, workspace: oak_lsp::workspace::WorkspaceManager::new(), sessions: DashMap::new() }
694 }
695
696 fn get_completion_context(nodes: &[TemplateNodeIR], pos: NargoPosition) -> Option<CompletionContext> {
697 for node in nodes {
698 match node {
699 TemplateNodeIR::Element(el) => {
700 if is_pos_in_span(pos, el.span) {
701 let tag_start = el.span.start;
703 let tag_name_end = NargoPosition {
704 line: tag_start.line,
705 column: tag_start.column + (el.tag.len() as u32) + 1, offset: 0,
707 };
708
709 if pos.line == tag_start.line && pos.column <= tag_name_end.column {
710 return Some(CompletionContext::Tag);
711 }
712
713 for attr in &el.attributes {
715 if is_pos_in_span(pos, attr.span) {
716 return Some(CompletionContext::Attribute(el.tag.clone()));
717 }
718 }
719
720 if let Some(ctx) = Self::get_completion_context(&el.children, pos) {
722 return Some(ctx);
723 }
724
725 return Some(CompletionContext::Attribute(el.tag.clone()));
727 }
728 }
729 TemplateNodeIR::Interpolation(expr) => {
730 if is_pos_in_span(pos, expr.span) {
731 return Some(CompletionContext::Expression);
732 }
733 }
734 _ => {}
735 }
736 }
737 None
738 }
739
740 fn get_script_program(nodes: &[TemplateNodeIR]) -> Option<JsProgram> {
741 for node in nodes {
742 if let TemplateNodeIR::Element(el) = node {
743 if el.tag == "script" {
744 if let Some(TemplateNodeIR::Text(content, _, _)) = el.children.first() {
745 let mut state = ParseState::new(content);
746 let ts_parser = nargo_parser::OakTypeScriptParser;
747 return ts_parser.parse(&mut state, "ts").ok();
748 }
749 }
750 if let Some(p) = Self::get_script_program(&el.children) {
751 return Some(p);
752 }
753 }
754 }
755 None
756 }
757
758 async fn find_definition_in_nodes(&self, nodes: &[TemplateNodeIR], pos: NargoPosition, uri: &str) -> Option<oak_lsp::types::LocationRange> {
759 let script_program = Self::get_script_program(nodes);
760
761 for node in nodes {
762 match node {
763 TemplateNodeIR::Element(el) => {
764 if is_pos_in_span(pos, el.span) {
765 if el.tag == "script" {
766 return self.find_definition_in_script(el, pos, uri).await;
767 }
768 else if el.tag == "style" {
769 return Self::find_definition_in_style(el, pos, uri);
770 }
771 else {
772 let res = self.find_definition_in_nodes_recursive(&el.children, pos, uri).await;
774 if res.is_some() {
775 return res;
776 }
777 }
778 }
779 }
780 TemplateNodeIR::Interpolation(expr) => {
781 if !expr.span.is_unknown() && is_pos_in_span(pos, expr.span) {
782 if let Some(symbol) = Self::find_symbol_in_template_expr(expr, pos) {
783 if let Some(program) = &script_program {
785 if let Some(def_span) = Self::find_definition_of_symbol(program, &symbol) {
786 if Self::is_import(program, &symbol) {
787 if let Some(loc) = self.find_external_definition(program, &symbol, uri).await {
788 return Some(loc);
789 }
790 }
791
792 if let Some(script_el_span) = Self::get_script_element_span(nodes) {
793 if let Some(source) = self.vfs.get_source(uri) {
794 let start_pos = OakPosition { line: script_el_span.start.line + def_span.start.line - 1, column: def_span.start.column - 1, offset: 0, length: 0 };
795 let end_pos = OakPosition { line: script_el_span.start.line + def_span.end.line - 1, column: def_span.end.column - 1, offset: 0, length: 0 };
796 return Some(oak_lsp::types::LocationRange { uri: uri.to_string().into(), range: Range { start: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, start_pos.line, start_pos.column)).unwrap_or(0), end: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, end_pos.line, end_pos.column)).unwrap_or(0) } });
797 }
798 }
799 }
800 }
801
802 if self.auto_imports.contains(&symbol) {
804 if let Some(loc) = self.find_implicit_definition(&symbol, "@nargo/core", uri).await {
805 return Some(loc);
806 }
807 }
808 }
809 }
810 }
811 _ => {}
812 }
813 }
814 None
815 }
816
817 fn find_definition_in_nodes_recursive<'a>(&'a self, nodes: &'a [TemplateNodeIR], pos: NargoPosition, uri: &'a str) -> std::pin::Pin<Box<dyn std::future::Future<Output = Option<oak_lsp::types::LocationRange>> + Send + 'a>> {
819 Box::pin(self.find_definition_in_nodes(nodes, pos, uri))
820 }
821
822 fn is_import(program: &JsProgram, symbol: &str) -> bool {
823 for stmt in &program.body {
824 if let JsStmt::Import { specifiers, .. } = stmt {
825 if specifiers.contains(&symbol.to_string()) {
826 return true;
827 }
828 }
829 }
830 false
831 }
832
833 fn get_script_element_span(nodes: &[TemplateNodeIR]) -> Option<NargoSpan> {
834 for node in nodes {
835 if let TemplateNodeIR::Element(el) = node {
836 if el.tag == "script" {
837 return Some(el.span);
838 }
839 if let Some(s) = Self::get_script_element_span(&el.children) {
840 return Some(s);
841 }
842 }
843 }
844 None
845 }
846
847 fn find_symbol_in_template_expr(expr: &ExpressionIR, pos: NargoPosition) -> Option<String> {
848 if !expr.span.is_unknown() && is_pos_in_span(pos, expr.span) {
850 return Some(expr.code.clone());
852 }
853 None
854 }
855
856 async fn find_definition_in_script(&self, el: &ElementIR, pos: NargoPosition, uri: &str) -> Option<oak_lsp::types::LocationRange> {
857 if let Some(TemplateNodeIR::Text(content, _, _)) = el.children.first() {
858 let mut state = ParseState::new(content);
859 let ts_parser = nargo_parser::OakTypeScriptParser;
860 if let Ok(program) = ts_parser.parse(&mut state, "ts") {
861 let el_span = el.span;
863 let relative_pos = NargoPosition { line: pos.line - el_span.start.line, column: if pos.line == el_span.start.line { pos.column - el_span.start.column } else { pos.column }, offset: 0 };
864
865 if let Some(symbol) = Self::find_symbol_at(&program, relative_pos) {
866 if Self::is_import(&program, &symbol) {
868 if let Some(loc) = self.find_external_definition(&program, &symbol, uri).await {
869 return Some(loc);
870 }
871 }
872
873 if let Some(def_span) = Self::find_definition_of_symbol(&program, &symbol) {
875 if let Some(source) = self.vfs.get_source(uri) {
876 let start_pos = OakPosition { line: el_span.start.line + def_span.start.line - 1, column: def_span.start.column - 1, offset: 0, length: 0 };
877 let end_pos = OakPosition { line: el_span.start.line + def_span.end.line - 1, column: def_span.end.column - 1, offset: 0, length: 0 };
878 return Some(oak_lsp::types::LocationRange { uri: uri.to_string().into(), range: Range { start: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, start_pos.line, start_pos.column)).unwrap_or(0), end: self.vfs.line_map(uri).map(|m| m.line_col_utf16_to_offset(&source, end_pos.line, end_pos.column)).unwrap_or(0) } });
879 }
880 }
881
882 if self.auto_imports.contains(&symbol) {
884 if let Some(loc) = self.find_implicit_definition(&symbol, "@nargo/core", uri).await {
885 return Some(loc);
886 }
887 }
888 }
889 }
890 }
891 None
892 }
893
894 fn find_symbol_at(program: &JsProgram, pos: NargoPosition) -> Option<String> {
895 for stmt in &program.body {
896 if let Some(symbol) = Self::find_symbol_in_stmt(stmt, pos) {
897 return Some(symbol);
898 }
899 }
900 None
901 }
902
903 fn find_symbol_in_stmt(stmt: &JsStmt, pos: NargoPosition) -> Option<String> {
904 use JsStmt::*;
905 match stmt {
906 VariableDecl { id, init, .. } => {
907 if id == "count" {
909 return Some(id.clone());
910 }
911 if let Some(init) = init {
912 return Self::find_symbol_in_expr(init, pos);
913 }
914 }
915 Expr(expr, ..) => return Self::find_symbol_in_expr(expr, pos),
916 _ => {}
917 }
918 None
919 }
920
921 fn find_symbol_in_expr(expr: &JsExpr, _pos: NargoPosition) -> Option<String> {
922 match expr {
923 JsExpr::Identifier(name, ..) => Some(name.clone()),
924 JsExpr::Call { callee, .. } => Self::find_symbol_in_expr(callee, _pos),
925 _ => None,
926 }
927 }
928
929 fn find_definition_of_symbol(program: &JsProgram, symbol: &str) -> Option<NargoSpan> {
930 for stmt in &program.body {
931 if let JsStmt::VariableDecl { id, span, .. } = stmt {
932 if id == symbol {
933 return Some(*span);
934 }
935 }
936 if let JsStmt::FunctionDecl { id, span, .. } = stmt {
937 if id == symbol {
938 return Some(*span);
939 }
940 }
941 }
942 None
943 }
944
945 async fn find_external_definition(&self, program: &JsProgram, symbol: &str, current_uri: &str) -> Option<oak_lsp::types::LocationRange> {
946 for stmt in &program.body {
947 if let JsStmt::Import { specifiers, source, .. } = stmt {
948 if specifiers.contains(&symbol.to_string()) {
949 return self.find_implicit_definition(symbol, source, current_uri).await;
950 }
951 }
952 }
953 None
954 }
955
956 async fn find_implicit_definition(&self, symbol: &str, source_path: &str, current_uri: &str) -> Option<oak_lsp::types::LocationRange> {
957 if let Some(url) = self.resolve_path(current_uri, source_path) {
958 if let Some(content) = self.get_document_content(&url).await {
959 let mut line = 0;
961 let mut col = 0;
962 if let Some(pos) = content.find(symbol) {
963 let before = &content[..pos];
964 line = before.lines().count().saturating_sub(1);
965 col = before.lines().last().map(|l| l.len()).unwrap_or(0);
966 }
967
968 if let Some(target_source) = self.vfs.get_source(url.as_str()) {
969 let start_pos = OakPosition { line: line as u32, column: col as u32, offset: 0, length: 0 };
970 let end_pos = OakPosition { line: line as u32, column: (col + symbol.len()) as u32, offset: 0, length: 0 };
971 return Some(oak_lsp::types::LocationRange { uri: url.to_string().into(), range: Range { start: self.vfs.line_map(url.as_str()).map(|m| m.line_col_utf16_to_offset(&target_source, start_pos.line, start_pos.column)).unwrap_or(0), end: self.vfs.line_map(url.as_str()).map(|m| m.line_col_utf16_to_offset(&target_source, end_pos.line, end_pos.column)).unwrap_or(0) } });
972 }
973 }
974 }
975 None
976 }
977
978 fn resolve_path(&self, current_uri: &str, relative_path: &str) -> Option<Url> {
979 let base_url = Url::parse(current_uri).ok()?;
980 let base_path = base_url.to_file_path().ok()?;
981
982 if relative_path == "@nargo/core" {
983 for (_, folder) in self.workspace.list_folders() {
985 let core_path = folder.join("runtimes/nargo-core/src/index.ts");
986 if core_path.exists() {
987 return Url::from_file_path(core_path).ok();
988 }
989 let core_path = folder.join("project-nargo/runtimes/nargo-core/src/index.ts");
991 if core_path.exists() {
992 return Url::from_file_path(core_path).ok();
993 }
994 }
995
996 if let Ok(uri) = Url::parse(current_uri) {
998 if let Ok(mut path) = uri.to_file_path() {
999 while let Some(parent) = path.parent() {
1000 let core_path = parent.join("runtimes/nargo-core/src/index.ts");
1001 if core_path.exists() {
1002 return Url::from_file_path(core_path).ok();
1003 }
1004 path = parent.to_path_buf();
1005 }
1006 }
1007 }
1008 }
1009
1010 let parent = base_path.parent()?;
1011 let target_path = if relative_path.starts_with('.') {
1012 parent.join(relative_path)
1013 }
1014 else {
1015 return None;
1016 };
1017
1018 let extensions = ["", ".ts", ".js"];
1019 for ext in extensions {
1020 let mut p = target_path.clone();
1021 if !ext.is_empty() {
1022 p.set_extension(ext.trim_start_matches('.'));
1023 }
1024 if p.exists() {
1025 return Url::from_file_path(p).ok();
1026 }
1027 }
1028
1029 None
1030 }
1031
1032 async fn get_document_content(&self, uri: &Url) -> Option<String> {
1033 let uri_str = uri.to_string();
1034 if let Some(source) = self.vfs.get_source(&uri_str) {
1035 return Some(source.text().to_string());
1036 }
1037
1038 if let Ok(path) = uri.to_file_path() {
1039 return std::fs::read_to_string(path).ok();
1040 }
1041
1042 None
1043 }
1044
1045 fn find_definition_in_style(_el: &ElementIR, _pos: NargoPosition, _uri: &str) -> Option<oak_lsp::types::LocationRange> {
1046 None
1048 }
1049}
1050
1051use nargo_compiler::Compiler;
1052use oak_core::Parser;
1053
1054use oak_lsp::types::{CompletionItem as OakCompletionItem, CompletionItemKind as OakCompletionItemKind, Diagnostic as OakDiagnostic, DiagnosticSeverity as OakDiagnosticSeverity};
1055
1056impl LanguageService for NargoLanguageService {
1057 type Lang = NargoLanguage;
1058 type Vfs = MemoryVfs;
1059
1060 fn vfs(&self) -> &Self::Vfs {
1061 &self.vfs
1062 }
1063
1064 fn workspace(&self) -> &oak_lsp::workspace::WorkspaceManager {
1065 &self.workspace
1066 }
1067
1068 fn get_root(&self, uri: &str) -> impl Future<Output = Option<RedNode<'_, NargoLanguage>>> + Send + '_ {
1069 let source = self.vfs.get_source(uri);
1070 let mut session = self.sessions.entry(uri.to_string()).or_insert_with(|| ParseSession::new(16));
1071 async move {
1072 if let Some(source) = source {
1073 }
1086 None
1087 }
1088 }
1089
1090 fn completion(&self, uri: &str, position: usize) -> impl Future<Output = Vec<OakCompletionItem>> + Send + '_ {
1091 let source = self.vfs.get_source(uri);
1092 let auto_imports = self.auto_imports.clone();
1093 let uri = uri.to_string();
1094
1095 async move {
1096 let mut items = Vec::new();
1097 if let Some(source) = source {
1098 let content = source.text();
1099 let pos = self
1100 .vfs
1101 .line_map(&uri)
1102 .map(|m| {
1103 let (l, c) = m.offset_to_line_col_utf16(&source, position);
1104 OakPosition { line: l, column: c, offset: 0, length: 0 }
1105 })
1106 .unwrap_or(OakPosition { line: 0, column: 0, offset: 0, length: 0 });
1107 let nargo_pos = NargoPosition { line: pos.line + 1, column: pos.column + 1, offset: 0 };
1108
1109 let mut state = ParseState::new(&content);
1110 let parser = VueTemplateParser;
1111 if let Ok(nodes) = parser.parse(&mut state, "html") {
1112 if let Some(context) = Self::get_completion_context(&nodes, nargo_pos) {
1113 match context {
1114 CompletionContext::Tag => {
1115 let tags = vec!["div", "span", "button", "input", "h1", "h2", "p", "section", "article"];
1116 for tag in tags {
1117 items.push(OakCompletionItem { label: tag.to_string(), kind: Some(OakCompletionItemKind::Keyword), detail: Some("HTML Tag".to_string()), documentation: None, insert_text: None });
1118 }
1119 }
1120 CompletionContext::Attribute(tag_name) => {
1121 let directives = vec!["@click", "@input", "@change", ":class", ":style", ":value"];
1122 for dir in directives {
1123 items.push(OakCompletionItem { label: dir.to_string(), kind: Some(OakCompletionItemKind::Constant), detail: Some("Directive".to_string()), documentation: None, insert_text: None });
1124 }
1125
1126 if tag_name == "input" {
1127 items.push(OakCompletionItem { label: "type".to_string(), kind: Some(OakCompletionItemKind::Property), detail: None, documentation: None, insert_text: None });
1128 }
1129 }
1130 CompletionContext::Expression => {
1131 for import in auto_imports.iter() {
1132 items.push(OakCompletionItem { label: import.clone(), kind: Some(OakCompletionItemKind::Function), detail: Some("Nargo Auto-import".to_string()), documentation: None, insert_text: None });
1133 }
1134
1135 if let Some(program) = Self::get_script_program(&nodes) {
1136 for stmt in &program.body {
1137 if let JsStmt::VariableDecl { id, .. } = stmt {
1138 items.push(OakCompletionItem { label: id.clone(), kind: Some(OakCompletionItemKind::Variable), detail: None, documentation: None, insert_text: None });
1139 }
1140 }
1141 }
1142 }
1143 }
1144 }
1145 }
1146 }
1147
1148 if items.is_empty() {
1149 let tags = vec!["div", "span", "button", "input", "h1", "h2", "p", "section", "article"];
1150 for tag in tags {
1151 items.push(OakCompletionItem { label: tag.to_string(), kind: Some(OakCompletionItemKind::Keyword), detail: Some("HTML Tag".to_string()), documentation: None, insert_text: None });
1152 }
1153 }
1154
1155 items
1156 }
1157 }
1158
1159 fn diagnostics(&self, uri: &str) -> impl Future<Output = Vec<OakDiagnostic>> + Send + '_ {
1160 let source = self.vfs.get_source(uri);
1161 let uri_parsed = Url::parse(uri).ok();
1162 let uri = uri.to_string();
1163
1164 async move {
1165 let mut diags = Vec::new();
1166 if let Some(source) = source {
1167 let content = source.text();
1168 let mut compiler = Compiler::new();
1169 let name = uri_parsed.as_ref().and_then(|u| u.path_segments()).and_then(|mut s| s.next_back()).unwrap_or("App.ts");
1170
1171 if let Err(e) = compiler.compile(name, &content) {
1172 let span = e.span();
1173 if !span.is_unknown() {
1174 let start_pos = OakPosition { line: span.start.line.saturating_sub(1), column: span.start.column.saturating_sub(1), offset: 0, length: 0 };
1175 let end_pos = OakPosition { line: span.end.line.saturating_sub(1), column: span.end.column.saturating_sub(1), offset: 0, length: 0 };
1176
1177 let start_offset = self.vfs.line_map(&uri).map(|m| m.line_col_utf16_to_offset(&source, start_pos.line, start_pos.column)).unwrap_or(0);
1178 let end_offset = self.vfs.line_map(&uri).map(|m| m.line_col_utf16_to_offset(&source, end_pos.line, end_pos.column)).unwrap_or(0);
1179
1180 diags.push(OakDiagnostic { range: Range { start: start_offset, end: end_offset }, severity: Some(OakDiagnosticSeverity::Error), message: format!("{}", e), source: Some("nargo-compiler".to_string()), code: None });
1181 }
1182 }
1183 }
1184 diags
1185 }
1186 }
1187
1188 fn initialize(&self, params: oak_lsp::types::InitializeParams) -> impl Future<Output = ()> + Send + '_ {
1189 async move {
1190 self.workspace.initialize(¶ms);
1191 }
1192 }
1193
1194 fn definition(&self, uri: &str, range: Range<usize>) -> impl Future<Output = Vec<oak_lsp::types::LocationRange>> + Send + '_ {
1195 let source = self.vfs.get_source(uri);
1196 let uri = uri.to_string();
1197 async move {
1198 if let Some(source) = source {
1199 let content = source.text();
1200 let position = self
1201 .vfs
1202 .line_map(&uri)
1203 .map(|m| {
1204 let (l, c) = m.offset_to_line_col_utf16(&source, range.start);
1205 OakPosition { line: l, column: c, offset: 0, length: 0 }
1206 })
1207 .unwrap_or(OakPosition { line: 0, column: 0, offset: 0, length: 0 });
1208 let nargo_pos = NargoPosition { line: position.line + 1, column: position.column + 1, offset: 0 };
1209
1210 let mut state = ParseState::new(&content);
1211 let parser = VueTemplateParser;
1212 if let Ok(nodes) = parser.parse(&mut state, "html") {
1213 if let Some(loc) = self.find_definition_in_nodes(&nodes, nargo_pos, &uri).await {
1214 return vec![loc];
1215 }
1216 }
1217 }
1218 vec![]
1219 }
1220 }
1221}
1222
1223pub async fn run_stdio() -> nargo_types::Result<()> {
1224 let stdin = tokio::io::stdin();
1225 let stdout = tokio::io::stdout();
1226
1227 let nargo_service = Arc::new(NargoLanguageService::new());
1228 let server = oak_lsp::LspServer::new(nargo_service);
1229 server.run(stdin, stdout).await.map_err(|e| nargo_types::Error::external_error("oak-lsp".to_string(), format!("{}", e), nargo_types::Span::unknown()))?;
1230 Ok(())
1231}
1232
1233pub async fn run_http_server(port: u16) -> nargo_types::Result<()> {
1234 println!("HTTP/WebSocket LSP server on port {} is not yet fully implemented.", port);
1235 println!("Falling back to stdio for now.");
1236 run_stdio().await
1237}