1mod capabilities;
7pub mod queries;
8pub mod types;
9mod uri_utils;
10
11#[cfg(not(target_arch = "wasm32"))]
13pub mod native;
14
15#[cfg(target_arch = "wasm32")]
17mod wasm;
18#[cfg(target_arch = "wasm32")]
19pub use wasm::WasmCore;
20
21pub use capabilities::server_capabilities;
23pub use queries::{LspDiagnostics, LspFileDiagnostics, LspSemanticTokens};
24pub use types::{CoreRequestId, Effect, LspError, LspOutput};
25
26use std::collections::{HashMap, HashSet};
27use std::path::PathBuf;
28
29use eure::query::{
30 CollectDiagnosticTargets, Glob, GlobResult, OpenDocuments, OpenDocumentsList, TextFile,
31 TextFileContent, Workspace, WorkspaceId, build_runtime,
32};
33use lsp_types::InitializeParams;
34use query_flow::{DurabilityLevel, QueryRuntime};
35
36use crate::types::{CommandQuery, CommandResult, FileDiagnosticsSubscription, PendingRequest};
37use crate::uri_utils::uri_to_text_file;
38
39use lsp_types::{
40 DidChangeTextDocumentParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
41 InitializeResult, PublishDiagnosticsParams, SemanticTokensParams,
42 notification::{
43 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument,
44 Notification as LspNotification, PublishDiagnostics,
45 },
46 request::{Initialize, Request as LspRequest, SemanticTokensFullRequest, Shutdown},
47};
48
49use crate::uri_utils::text_file_to_uri;
50use query_flow::QueryError;
51use serde_json::Value;
52
53#[cfg(not(target_arch = "wasm32"))]
55use tracing::{debug, error};
56
57#[cfg(target_arch = "wasm32")]
58macro_rules! debug {
59 ($($arg:tt)*) => { web_sys::console::debug_1(&format!($($arg)*).into()) };
60}
61#[cfg(target_arch = "wasm32")]
62macro_rules! error {
63 ($($arg:tt)*) => { web_sys::console::error_1(&format!($($arg)*).into()) };
64}
65
66pub fn register_workspaces_from_init(runtime: &mut QueryRuntime, params: &InitializeParams) {
68 if let Some(folders) = ¶ms.workspace_folders {
69 for folder in folders {
70 let workspace_path = PathBuf::from(folder.uri.path().as_str());
71 let config_path = workspace_path.join("Eure.eure");
72
73 runtime.resolve_asset(
74 WorkspaceId(workspace_path.to_string_lossy().into_owned()),
75 Workspace {
76 path: workspace_path,
77 config_path,
78 },
79 DurabilityLevel::Static,
80 );
81 }
82 } else if let Some(root_uri) = {
83 #[allow(
84 deprecated,
85 reason = "fallback for clients without workspace_folders support"
86 )]
87 ¶ms.root_uri
88 } {
89 let workspace_path = PathBuf::from(root_uri.path().as_str());
90 let config_path = workspace_path.join("Eure.eure");
91
92 runtime.resolve_asset(
93 WorkspaceId(workspace_path.to_string_lossy().into_owned()),
94 Workspace {
95 path: workspace_path,
96 config_path,
97 },
98 DurabilityLevel::Static,
99 );
100 }
101}
102
103pub struct LspCore {
109 runtime: QueryRuntime,
111 pending_requests: HashMap<CoreRequestId, PendingRequest>,
113 pending_assets: HashSet<TextFile>,
115 pending_globs: HashMap<String, Glob>,
117 diagnostics_subscriptions: HashMap<TextFile, FileDiagnosticsSubscription>,
119 published_uris: HashSet<String>,
121 documents: HashMap<String, String>,
123 initialized: bool,
125}
126
127impl LspCore {
128 pub fn new() -> Self {
130 let runtime = build_runtime();
131
132 Self {
133 runtime,
134 pending_requests: HashMap::new(),
135 pending_assets: HashSet::new(),
136 pending_globs: HashMap::new(),
137 diagnostics_subscriptions: HashMap::new(),
138 published_uris: HashSet::new(),
139 documents: HashMap::new(),
140 initialized: false,
141 }
142 }
143
144 pub fn runtime_mut(&mut self) -> &mut QueryRuntime {
148 &mut self.runtime
149 }
150
151 pub fn is_initialized(&self) -> bool {
153 self.initialized
154 }
155
156 pub fn set_initialized(&mut self) {
158 self.initialized = true;
159 }
160
161 pub fn pending_files(&self) -> impl Iterator<Item = &TextFile> {
163 self.pending_assets.iter()
164 }
165
166 pub fn pending_globs(&self) -> impl Iterator<Item = (&str, &Glob)> {
168 self.pending_globs.iter().map(|(k, v)| (k.as_str(), v))
169 }
170
171 fn update_open_documents(&mut self) {
178 let files: Vec<TextFile> = self
179 .documents
180 .keys()
181 .filter_map(|uri| uri_to_text_file(uri).ok())
182 .collect();
183
184 self.runtime.resolve_asset(
185 OpenDocuments,
186 OpenDocumentsList(files),
187 DurabilityLevel::Volatile,
188 );
189 }
190
191 pub fn open_document(&mut self, uri: &str, content: String) {
195 self.documents.insert(uri.to_string(), content.clone());
197
198 let Ok(file) = uri_to_text_file(uri) else {
200 return; };
202 self.runtime
203 .resolve_asset(file, TextFileContent(content), DurabilityLevel::Volatile);
204
205 self.update_open_documents();
207 }
208
209 pub fn change_document(&mut self, uri: &str, content: String) {
213 self.open_document(uri, content);
215 }
216
217 pub fn close_document(&mut self, uri: &str) {
221 self.documents.remove(uri);
223
224 if let Ok(file) = uri_to_text_file(uri) {
226 self.runtime.invalidate_asset(&file);
227 }
228
229 self.update_open_documents();
231 }
232
233 pub fn get_document(&self, uri: &str) -> Option<&String> {
235 self.documents.get(uri)
236 }
237
238 pub fn handle_request(
244 &mut self,
245 id: CoreRequestId,
246 method: &str,
247 params: Value,
248 ) -> (Vec<LspOutput>, Vec<Effect>) {
249 let mut outputs = Vec::new();
250 let mut effects = Vec::new();
251
252 match method {
253 Initialize::METHOD => {
254 let init_params: InitializeParams = match serde_json::from_value(params) {
255 Ok(p) => p,
256 Err(e) => {
257 outputs.push(LspOutput::Response {
258 id,
259 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
260 });
261 return (outputs, effects);
262 }
263 };
264
265 register_workspaces_from_init(&mut self.runtime, &init_params);
267
268 let result = InitializeResult {
269 capabilities: server_capabilities(),
270 server_info: Some(lsp_types::ServerInfo {
271 name: "eure-ls".to_string(),
272 version: Some(env!("CARGO_PKG_VERSION").to_string()),
273 }),
274 };
275
276 self.initialized = true;
277 outputs.push(LspOutput::Response {
278 id,
279 result: Ok(serde_json::to_value(result).unwrap()),
280 });
281 }
282 Shutdown::METHOD => {
283 outputs.push(LspOutput::Response {
284 id,
285 result: Ok(Value::Null),
286 });
287 }
288 SemanticTokensFullRequest::METHOD => {
289 let params: SemanticTokensParams = match serde_json::from_value(params) {
290 Ok(p) => p,
291 Err(e) => {
292 outputs.push(LspOutput::Response {
293 id,
294 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
295 });
296 return (outputs, effects);
297 }
298 };
299
300 let uri = params.text_document.uri;
301 let uri_str = uri.as_str();
302 let file = match uri_to_text_file(uri_str) {
303 Ok(f) => f,
304 Err(e) => {
305 outputs.push(LspOutput::Response {
306 id,
307 result: Err(LspError::invalid_params(format!("Invalid URI: {}", e))),
308 });
309 return (outputs, effects);
310 }
311 };
312 let source = self.documents.get(uri_str).cloned().unwrap_or_default();
313
314 let query = LspSemanticTokens::new(file, source.clone());
315 let command = CommandQuery::SemanticTokensFull(query);
316
317 match self.try_execute(&command) {
318 Ok(result) => {
319 let json = self.result_to_value(result);
320 outputs.push(LspOutput::Response {
321 id,
322 result: Ok(json),
323 });
324 }
325 Err(QueryError::Suspend { .. }) => {
326 let (new_effects, waiting_for) = self.collect_pending_assets();
328 effects.extend(new_effects);
329
330 self.pending_requests.insert(
331 id.clone(),
332 PendingRequest {
333 id,
334 command,
335 waiting_for,
336 },
337 );
338 }
339 Err(e) => {
340 if let Some(lsp_err) = Self::handle_query_error("SemanticTokens", e) {
341 outputs.push(LspOutput::Response {
342 id,
343 result: Err(lsp_err),
344 });
345 }
346 }
347 }
348 }
349 _ => {
350 outputs.push(LspOutput::Response {
351 id,
352 result: Err(LspError::method_not_found(method)),
353 });
354 }
355 }
356
357 (outputs, effects)
358 }
359
360 pub fn cancel_request(&mut self, id: &CoreRequestId) {
362 self.pending_requests.remove(id);
363 }
364
365 pub fn handle_notification(
371 &mut self,
372 method: &str,
373 params: Value,
374 ) -> (Vec<LspOutput>, Vec<Effect>) {
375 let mut outputs = Vec::new();
376 let mut effects = Vec::new();
377
378 match method {
379 DidOpenTextDocument::METHOD => {
380 if let Ok(params) = serde_json::from_value::<DidOpenTextDocumentParams>(params) {
381 let uri = params.text_document.uri;
382 let content = params.text_document.text;
383
384 self.open_document(uri.as_str(), content);
386
387 let (diag_outputs, diag_effects) = self.refresh_diagnostics();
389 outputs.extend(diag_outputs);
390 effects.extend(diag_effects);
391 }
392 }
393 DidChangeTextDocument::METHOD => {
394 if let Ok(params) = serde_json::from_value::<DidChangeTextDocumentParams>(params) {
395 let uri = params.text_document.uri;
396 if let Some(change) = params.content_changes.into_iter().next() {
398 let content = change.text;
399
400 self.change_document(uri.as_str(), content);
402
403 let (diag_outputs, diag_effects) = self.refresh_diagnostics();
405 outputs.extend(diag_outputs);
406 effects.extend(diag_effects);
407 }
408 }
409 }
410 DidCloseTextDocument::METHOD => {
411 if let Ok(params) = serde_json::from_value::<DidCloseTextDocumentParams>(params) {
412 let uri = params.text_document.uri;
413 let uri_str = uri.as_str();
414
415 self.close_document(uri_str);
417
418 self.pending_requests
420 .retain(|_, pending| match &pending.command {
421 CommandQuery::SemanticTokensFull(q) => {
422 let pending_uri = text_file_to_uri(&q.file);
423 pending_uri != uri_str
424 }
425 });
426
427 let (diag_outputs, diag_effects) = self.refresh_diagnostics();
429 outputs.extend(diag_outputs);
430 effects.extend(diag_effects);
431 }
432 }
433 "$/cancelRequest" => {
434 if let Some(id) = params.get("id") {
435 let core_id = CoreRequestId::from(id);
436 self.cancel_request(&core_id);
437 }
438 }
439 "initialized" | "exit" => {
440 }
442 _ => {
443 }
445 }
446
447 (outputs, effects)
448 }
449
450 fn refresh_diagnostics(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
457 let mut outputs = Vec::new();
458 let mut effects = Vec::new();
459
460 debug!("[LspCore] refresh_diagnostics");
461
462 let all_files = match self.runtime.poll(CollectDiagnosticTargets::new()) {
464 Ok(polled) => match polled.value {
465 Ok(files) => files,
466 Err(e) => {
467 error!("CollectDiagnosticTargets error: {}", e);
468 return (outputs, effects);
469 }
470 },
471 Err(QueryError::Suspend { .. }) => {
472 debug!("[LspCore] CollectDiagnosticTargets suspended");
473 let (new_effects, _) = self.collect_pending_assets();
474 effects.extend(new_effects);
475 return (outputs, effects);
476 }
477 Err(e) => {
478 Self::handle_query_error("CollectDiagnosticTargets", e);
479 return (outputs, effects);
480 }
481 };
482
483 debug!("[LspCore] diagnostic targets: {} files", all_files.len());
484
485 let mut current_uris = HashSet::new();
487 for file in all_files.iter() {
488 let query = LspFileDiagnostics::new(file.clone());
489
490 let last_revision = self
492 .diagnostics_subscriptions
493 .get(file)
494 .map(|s| s.last_revision)
495 .unwrap_or_default();
496
497 match self.runtime.poll(query.clone()) {
498 Ok(polled) => {
499 let uri = text_file_to_uri(file);
500 current_uris.insert(uri.clone());
501
502 if polled.revision != last_revision {
504 self.diagnostics_subscriptions.insert(
506 file.clone(),
507 FileDiagnosticsSubscription {
508 file: file.clone(),
509 query,
510 last_revision: polled.revision,
511 },
512 );
513
514 match polled.value {
515 Ok(diagnostics) => {
516 debug!(
517 "[LspCore] sending {} diagnostics for {}",
518 diagnostics.len(),
519 uri
520 );
521 if let Ok(parsed_uri) = uri.parse::<lsp_types::Uri>() {
522 let params = PublishDiagnosticsParams {
523 uri: parsed_uri,
524 diagnostics: diagnostics.as_ref().clone(),
525 version: None,
526 };
527 outputs.push(LspOutput::Notification {
528 method: PublishDiagnostics::METHOD.to_string(),
529 params: serde_json::to_value(params).unwrap(),
530 });
531 }
532 }
533 Err(e) => {
534 error!("Diagnostics query error for {}: {}", uri, e);
535 if let Ok(parsed_uri) = uri.parse::<lsp_types::Uri>() {
536 let params = PublishDiagnosticsParams {
537 uri: parsed_uri,
538 diagnostics: vec![],
539 version: None,
540 };
541 outputs.push(LspOutput::Notification {
542 method: PublishDiagnostics::METHOD.to_string(),
543 params: serde_json::to_value(params).unwrap(),
544 });
545 }
546 }
547 }
548 }
549 }
550 Err(QueryError::Suspend { .. }) => {
551 debug!("[LspCore] diagnostics for {:?} suspended", file);
552 self.diagnostics_subscriptions.insert(
554 file.clone(),
555 FileDiagnosticsSubscription {
556 file: file.clone(),
557 query,
558 last_revision,
559 },
560 );
561 let (new_effects, _) = self.collect_pending_assets();
562 effects.extend(new_effects);
563 }
564 Err(e) => {
565 Self::handle_query_error(&format!("LspFileDiagnostics({:?})", file), e);
566 }
567 }
568 }
569
570 let stale: Vec<_> = self
572 .published_uris
573 .difference(¤t_uris)
574 .cloned()
575 .collect();
576 for uri in stale {
577 debug!("[LspCore] clearing stale diagnostics for {}", uri);
578 if let Ok(parsed_uri) = uri.parse::<lsp_types::Uri>() {
579 let params = PublishDiagnosticsParams {
580 uri: parsed_uri,
581 diagnostics: vec![],
582 version: None,
583 };
584 outputs.push(LspOutput::Notification {
585 method: PublishDiagnostics::METHOD.to_string(),
586 params: serde_json::to_value(params).unwrap(),
587 });
588 }
589 }
590 self.published_uris = current_uris;
591
592 self.diagnostics_subscriptions
594 .retain(|f, _| all_files.contains(f));
595
596 (outputs, effects)
597 }
598
599 pub fn resolve_file(
605 &mut self,
606 file: TextFile,
607 content: Result<String, String>,
608 ) -> (Vec<LspOutput>, Vec<Effect>) {
609 match content {
611 Ok(text) => {
612 self.runtime.resolve_asset(
613 file.clone(),
614 TextFileContent(text),
615 DurabilityLevel::Volatile,
616 );
617 }
618 Err(error) => {
619 self.runtime.resolve_asset_error::<TextFile>(
620 file.clone(),
621 anyhow::anyhow!("{}", error),
622 DurabilityLevel::Volatile,
623 );
624 }
625 }
626 self.pending_assets.remove(&file);
627
628 self.process_after_asset_change()
630 }
631
632 pub fn resolve_glob(
636 &mut self,
637 id: &str,
638 files: Vec<TextFile>,
639 ) -> (Vec<LspOutput>, Vec<Effect>) {
640 if let Some(glob_key) = self.pending_globs.remove(id) {
641 self.runtime
642 .resolve_asset(glob_key, GlobResult(files), DurabilityLevel::Volatile);
643 }
644
645 self.process_after_asset_change()
647 }
648
649 fn process_after_asset_change(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
651 let mut outputs = Vec::new();
652 let mut effects = Vec::new();
653
654 let (req_outputs, req_effects) = self.retry_pending_requests();
656 outputs.extend(req_outputs);
657 effects.extend(req_effects);
658
659 let (diag_outputs, diag_effects) = self.check_diagnostics_subscriptions();
661 outputs.extend(diag_outputs);
662 effects.extend(diag_effects);
663
664 (outputs, effects)
665 }
666
667 fn retry_pending_requests(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
669 let mut outputs = Vec::new();
670 let mut effects = Vec::new();
671
672 let request_ids: Vec<CoreRequestId> = self.pending_requests.keys().cloned().collect();
673 let mut completed_ids = Vec::new();
674
675 for id in request_ids {
676 if let Some(pending) = self.pending_requests.get(&id) {
677 let command = pending.command.clone();
678
679 match self.try_execute(&command) {
680 Ok(result) => {
681 let json = self.result_to_value(result);
682 outputs.push(LspOutput::Response {
683 id: id.clone(),
684 result: Ok(json),
685 });
686 completed_ids.push(id);
687 }
688 Err(QueryError::Suspend { .. }) => {
689 let (new_effects, _) = self.collect_pending_assets();
691 effects.extend(new_effects);
692 }
693 Err(e) => {
694 if let Some(lsp_err) = Self::handle_query_error("RetryQuery", e) {
695 outputs.push(LspOutput::Response {
696 id: id.clone(),
697 result: Err(lsp_err),
698 });
699 completed_ids.push(id);
700 }
701 }
702 }
703 }
704 }
705
706 for id in completed_ids {
707 self.pending_requests.remove(&id);
708 }
709
710 (outputs, effects)
711 }
712
713 fn check_diagnostics_subscriptions(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
717 self.refresh_diagnostics()
718 }
719
720 fn handle_query_error(context: &str, err: QueryError) -> Option<LspError> {
725 match err {
726 QueryError::Suspend { .. } => None,
727 QueryError::Cancelled => {
728 error!("{}: query unexpectedly cancelled", context);
729 Some(LspError::internal_error("Query cancelled"))
730 }
731 QueryError::DependenciesRemoved { missing_keys } => {
732 error!("{}: dependencies removed: {:?}", context, missing_keys);
733 Some(LspError::internal_error("Dependencies removed"))
734 }
735 QueryError::Cycle { path } => {
736 error!("{}: query cycle: {:?}", context, path);
737 Some(LspError::internal_error(format!("Query cycle: {:?}", path)))
738 }
739 QueryError::InconsistentAssetResolution => {
740 unreachable!("InconsistentAssetResolution should not occur")
741 }
742 QueryError::UserError(e) => {
743 error!("{}: unexpected user error: {}", context, e);
744 Some(LspError::internal_error(e.to_string()))
745 }
746 }
747 }
748
749 fn try_execute(&mut self, command: &CommandQuery) -> Result<CommandResult, QueryError> {
751 match command {
752 CommandQuery::SemanticTokensFull(query) => {
753 let result = self.runtime.query(query.clone())?;
754 Ok(CommandResult::SemanticTokens(Some((*result).clone())))
755 }
756 }
757 }
758
759 fn result_to_value(&self, result: CommandResult) -> Value {
761 match result {
762 CommandResult::SemanticTokens(tokens) => {
763 serde_json::to_value(tokens).unwrap_or(Value::Null)
764 }
765 }
766 }
767
768 fn collect_pending_assets(&mut self) -> (Vec<Effect>, HashSet<TextFile>) {
770 let mut effects = Vec::new();
771 let mut waiting_for = HashSet::new();
772
773 for pending in self.runtime.pending_assets() {
774 if let Some(file) = pending.key::<TextFile>() {
775 if !self.pending_assets.contains(file) {
776 self.pending_assets.insert(file.clone());
777 effects.push(Effect::FetchFile(file.clone()));
778 }
779 waiting_for.insert(file.clone());
780 } else if let Some(glob_key) = pending.key::<Glob>() {
781 let id = format!(
783 "{}:{}",
784 glob_key.base_dir.to_string_lossy(),
785 glob_key.pattern
786 );
787 if !self.pending_globs.contains_key(&id) {
788 self.pending_globs.insert(id.clone(), glob_key.clone());
789 effects.push(Effect::ExpandGlob {
790 id,
791 glob: glob_key.clone(),
792 });
793 }
794 }
795 }
796
797 (effects, waiting_for)
798 }
799}
800
801impl Default for LspCore {
802 fn default() -> Self {
803 Self::new()
804 }
805}