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::{
24 LspDiagnostics, LspFileDiagnostics, LspSemanticTokens, lsp_completion, lsp_definition,
25 lsp_hover, position_to_offset,
26};
27pub use types::{CoreRequestId, Effect, LspError, LspOutput};
28
29use std::collections::{HashMap, HashSet};
30use std::path::PathBuf;
31
32use eure::query::{
33 CollectDiagnosticTargets, Glob, GlobResult, OpenDocuments, OpenDocumentsList, TextFile,
34 TextFileContent, Workspace, WorkspaceId, build_runtime,
35};
36use lsp_types::InitializeParams;
37use query_flow::{DurabilityLevel, QueryRuntime};
38
39use crate::types::{
40 CommandQuery, CommandResult, CompletionRequest, DefinitionRequest, FileDiagnosticsSubscription,
41 HoverRequest, PendingRequest,
42};
43use crate::uri_utils::uri_to_text_file;
44
45use lsp_types::{
46 CompletionParams, CompletionResponse, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
47 DidOpenTextDocumentParams, HoverParams, InitializeResult, PublishDiagnosticsParams,
48 SemanticTokensParams,
49 notification::{
50 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument,
51 Notification as LspNotification, PublishDiagnostics,
52 },
53 request::{
54 Completion, HoverRequest as HoverLspRequest, Initialize, Request as LspRequest,
55 SemanticTokensFullRequest, Shutdown,
56 },
57};
58
59use crate::uri_utils::text_file_to_uri;
60use query_flow::{Db, QueryError};
61use serde_json::Value;
62
63#[cfg(not(target_arch = "wasm32"))]
65use tracing::{debug, error};
66
67#[cfg(target_arch = "wasm32")]
68macro_rules! debug {
69 ($($arg:tt)*) => { web_sys::console::debug_1(&format!($($arg)*).into()) };
70}
71#[cfg(target_arch = "wasm32")]
72macro_rules! error {
73 ($($arg:tt)*) => { web_sys::console::error_1(&format!($($arg)*).into()) };
74}
75
76pub fn register_workspaces_from_init(runtime: &mut QueryRuntime, params: &InitializeParams) {
78 if let Some(folders) = ¶ms.workspace_folders {
79 for folder in folders {
80 let workspace_path = PathBuf::from(folder.uri.path().as_str());
81 let config_path = workspace_path.join("Eure.eure");
82
83 runtime.resolve_asset(
84 WorkspaceId(workspace_path.to_string_lossy().into_owned()),
85 Workspace {
86 path: workspace_path,
87 config_path,
88 },
89 DurabilityLevel::Static,
90 );
91 }
92 } else if let Some(root_uri) = {
93 #[allow(
94 deprecated,
95 reason = "fallback for clients without workspace_folders support"
96 )]
97 ¶ms.root_uri
98 } {
99 let workspace_path = PathBuf::from(root_uri.path().as_str());
100 let config_path = workspace_path.join("Eure.eure");
101
102 runtime.resolve_asset(
103 WorkspaceId(workspace_path.to_string_lossy().into_owned()),
104 Workspace {
105 path: workspace_path,
106 config_path,
107 },
108 DurabilityLevel::Static,
109 );
110 }
111}
112
113pub struct LspCore {
119 runtime: QueryRuntime,
121 pending_requests: HashMap<CoreRequestId, PendingRequest>,
123 pending_assets: HashSet<TextFile>,
125 pending_globs: HashMap<String, Glob>,
127 diagnostics_subscriptions: HashMap<TextFile, FileDiagnosticsSubscription>,
129 published_uris: HashSet<String>,
131 documents: HashMap<String, String>,
133 initialized: bool,
135 definition_link_support: bool,
136}
137
138impl LspCore {
139 pub fn new() -> Self {
141 let runtime = build_runtime();
142
143 Self {
144 runtime,
145 pending_requests: HashMap::new(),
146 pending_assets: HashSet::new(),
147 pending_globs: HashMap::new(),
148 diagnostics_subscriptions: HashMap::new(),
149 published_uris: HashSet::new(),
150 documents: HashMap::new(),
151 initialized: false,
152 definition_link_support: false,
153 }
154 }
155
156 pub fn runtime_mut(&mut self) -> &mut QueryRuntime {
160 &mut self.runtime
161 }
162
163 pub fn is_initialized(&self) -> bool {
165 self.initialized
166 }
167
168 pub fn configure_client(&mut self, capabilities: &lsp_types::ClientCapabilities) {
170 self.definition_link_support = capabilities
171 .text_document
172 .as_ref()
173 .and_then(|caps| caps.definition.as_ref())
174 .and_then(|caps| caps.link_support)
175 .unwrap_or(false);
176 }
177
178 pub fn set_initialized(&mut self) {
180 self.initialized = true;
181 }
182
183 pub fn pending_files(&self) -> impl Iterator<Item = &TextFile> {
185 self.pending_assets.iter()
186 }
187
188 pub fn pending_globs(&self) -> impl Iterator<Item = (&str, &Glob)> {
190 self.pending_globs.iter().map(|(k, v)| (k.as_str(), v))
191 }
192
193 fn update_open_documents(&mut self) {
200 let files: Vec<TextFile> = self
201 .documents
202 .keys()
203 .filter_map(|uri| uri_to_text_file(uri).ok())
204 .collect();
205
206 self.runtime.resolve_asset(
207 OpenDocuments,
208 OpenDocumentsList(files),
209 DurabilityLevel::Volatile,
210 );
211 }
212
213 pub fn open_document(&mut self, uri: &str, content: String) {
217 self.documents.insert(uri.to_string(), content.clone());
219
220 let Ok(file) = uri_to_text_file(uri) else {
222 return; };
224 self.runtime
225 .resolve_asset(file, TextFileContent(content), DurabilityLevel::Volatile);
226
227 self.update_open_documents();
229 }
230
231 pub fn change_document(&mut self, uri: &str, content: String) {
235 self.open_document(uri, content);
237 }
238
239 pub fn close_document(&mut self, uri: &str) {
243 self.documents.remove(uri);
245
246 if let Ok(file) = uri_to_text_file(uri) {
248 if file.as_local_path().is_some() {
251 self.runtime.invalidate_asset(&file);
252 }
253 }
254
255 self.update_open_documents();
257 }
258
259 pub fn get_document(&self, uri: &str) -> Option<&String> {
261 self.documents.get(uri)
262 }
263
264 pub fn handle_request(
270 &mut self,
271 id: CoreRequestId,
272 method: &str,
273 params: Value,
274 ) -> (Vec<LspOutput>, Vec<Effect>) {
275 let mut outputs = Vec::new();
276 let mut effects = Vec::new();
277
278 match method {
279 Initialize::METHOD => {
280 let init_params: InitializeParams = match serde_json::from_value(params) {
281 Ok(p) => p,
282 Err(e) => {
283 outputs.push(LspOutput::Response {
284 id,
285 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
286 });
287 return (outputs, effects);
288 }
289 };
290
291 self.configure_client(&init_params.capabilities);
292
293 register_workspaces_from_init(&mut self.runtime, &init_params);
295
296 let result = InitializeResult {
297 capabilities: server_capabilities(),
298 server_info: Some(lsp_types::ServerInfo {
299 name: "eure-ls".to_string(),
300 version: Some(env!("CARGO_PKG_VERSION").to_string()),
301 }),
302 };
303
304 self.initialized = true;
305 outputs.push(LspOutput::Response {
306 id,
307 result: Ok(serde_json::to_value(result).unwrap()),
308 });
309 }
310 Shutdown::METHOD => {
311 outputs.push(LspOutput::Response {
312 id,
313 result: Ok(Value::Null),
314 });
315 }
316 SemanticTokensFullRequest::METHOD => {
317 let params: SemanticTokensParams = match serde_json::from_value(params) {
318 Ok(p) => p,
319 Err(e) => {
320 outputs.push(LspOutput::Response {
321 id,
322 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
323 });
324 return (outputs, effects);
325 }
326 };
327
328 let uri = params.text_document.uri;
329 let uri_str = uri.as_str();
330 let file = match uri_to_text_file(uri_str) {
331 Ok(f) => f,
332 Err(e) => {
333 outputs.push(LspOutput::Response {
334 id,
335 result: Err(LspError::invalid_params(format!("Invalid URI: {}", e))),
336 });
337 return (outputs, effects);
338 }
339 };
340 let source = self.documents.get(uri_str).cloned().unwrap_or_default();
341
342 let query = LspSemanticTokens::new(file, source.clone());
343 let command = CommandQuery::SemanticTokensFull(query);
344 let (cmd_outputs, cmd_effects) = self.run_command(id, command);
345 outputs.extend(cmd_outputs);
346 effects.extend(cmd_effects);
347 }
348 Completion::METHOD => {
349 let params: CompletionParams = match serde_json::from_value(params) {
350 Ok(p) => p,
351 Err(e) => {
352 outputs.push(LspOutput::Response {
353 id,
354 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
355 });
356 return (outputs, effects);
357 }
358 };
359
360 let position = params.text_document_position;
361 let uri_str = position.text_document.uri.as_str();
362 let file = match uri_to_text_file(uri_str) {
363 Ok(f) => f,
364 Err(e) => {
365 outputs.push(LspOutput::Response {
366 id,
367 result: Err(LspError::invalid_params(format!("Invalid URI: {}", e))),
368 });
369 return (outputs, effects);
370 }
371 };
372 let source = self.documents.get(uri_str).cloned().unwrap_or_default();
375 let offset = position_to_offset(&source, position.position) as u32;
376
377 let command = CommandQuery::Completion(CompletionRequest { file, offset });
378 let (cmd_outputs, cmd_effects) = self.run_command(id, command);
379 outputs.extend(cmd_outputs);
380 effects.extend(cmd_effects);
381 }
382 HoverLspRequest::METHOD => {
383 let params: HoverParams = match serde_json::from_value(params) {
384 Ok(p) => p,
385 Err(e) => {
386 outputs.push(LspOutput::Response {
387 id,
388 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
389 });
390 return (outputs, effects);
391 }
392 };
393
394 let position = params.text_document_position_params;
395 let uri_str = position.text_document.uri.as_str();
396 let file = match uri_to_text_file(uri_str) {
397 Ok(f) => f,
398 Err(e) => {
399 outputs.push(LspOutput::Response {
400 id,
401 result: Err(LspError::invalid_params(format!("Invalid URI: {}", e))),
402 });
403 return (outputs, effects);
404 }
405 };
406 let source = self.documents.get(uri_str).cloned().unwrap_or_default();
407 let offset = position_to_offset(&source, position.position) as u32;
408
409 let command = CommandQuery::Hover(HoverRequest { file, offset });
410 let (cmd_outputs, cmd_effects) = self.run_command(id, command);
411 outputs.extend(cmd_outputs);
412 effects.extend(cmd_effects);
413 }
414 "textDocument/definition" => {
415 let params: lsp_types::GotoDefinitionParams = match serde_json::from_value(params) {
416 Ok(p) => p,
417 Err(e) => {
418 outputs.push(LspOutput::Response {
419 id,
420 result: Err(LspError::invalid_params(format!("Invalid params: {}", e))),
421 });
422 return (outputs, effects);
423 }
424 };
425
426 let position = params.text_document_position_params;
427 let uri_str = position.text_document.uri.as_str();
428 let file = match uri_to_text_file(uri_str) {
429 Ok(f) => f,
430 Err(e) => {
431 outputs.push(LspOutput::Response {
432 id,
433 result: Err(LspError::invalid_params(format!("Invalid URI: {}", e))),
434 });
435 return (outputs, effects);
436 }
437 };
438 let command = CommandQuery::Definition(DefinitionRequest {
439 file,
440 position: position.position,
441 });
442 let (cmd_outputs, cmd_effects) = self.run_command(id, command);
443 outputs.extend(cmd_outputs);
444 effects.extend(cmd_effects);
445 }
446 "eure/schemaContent" => {
447 let params: lsp_types::TextDocumentIdentifier = match serde_json::from_value(params)
448 {
449 Ok(params) => params,
450 Err(error) => {
451 outputs.push(LspOutput::Response {
452 id,
453 result: Err(LspError::invalid_params(error.to_string())),
454 });
455 return (outputs, effects);
456 }
457 };
458 let file = match uri_to_text_file(params.uri.as_str()) {
459 Ok(file) if file.as_url().is_some() => file,
460 _ => {
461 outputs.push(LspOutput::Response {
462 id,
463 result: Err(LspError::invalid_params("Expected an HTTPS schema URI")),
464 });
465 return (outputs, effects);
466 }
467 };
468 let (cmd_outputs, cmd_effects) =
469 self.run_command(id, CommandQuery::SchemaContent(file));
470 outputs.extend(cmd_outputs);
471 effects.extend(cmd_effects);
472 }
473 _ => {
474 outputs.push(LspOutput::Response {
475 id,
476 result: Err(LspError::method_not_found(method)),
477 });
478 }
479 }
480
481 (outputs, effects)
482 }
483
484 fn run_command(
487 &mut self,
488 id: CoreRequestId,
489 command: CommandQuery,
490 ) -> (Vec<LspOutput>, Vec<Effect>) {
491 let mut outputs = Vec::new();
492 let mut effects = Vec::new();
493
494 match self.try_execute(&command) {
495 Ok(result) => {
496 let json = self.result_to_value(result);
497 outputs.push(LspOutput::Response {
498 id,
499 result: Ok(json),
500 });
501 }
502 Err(QueryError::Suspend { .. }) => {
503 let (new_effects, waiting_for) = self.collect_pending_assets();
505 effects.extend(new_effects);
506
507 self.pending_requests.insert(
508 id.clone(),
509 PendingRequest {
510 id,
511 command,
512 waiting_for,
513 },
514 );
515 }
516 Err(e) => {
517 if let Some(lsp_err) = Self::handle_query_error(command.name(), e) {
518 outputs.push(LspOutput::Response {
519 id,
520 result: Err(lsp_err),
521 });
522 }
523 }
524 }
525
526 (outputs, effects)
527 }
528
529 pub fn cancel_request(&mut self, id: &CoreRequestId) {
531 self.pending_requests.remove(id);
532 }
533
534 pub fn handle_notification(
540 &mut self,
541 method: &str,
542 params: Value,
543 ) -> (Vec<LspOutput>, Vec<Effect>) {
544 let mut outputs = Vec::new();
545 let mut effects = Vec::new();
546
547 match method {
548 DidOpenTextDocument::METHOD => {
549 if let Ok(params) = serde_json::from_value::<DidOpenTextDocumentParams>(params) {
550 let uri = params.text_document.uri;
551 let content = params.text_document.text;
552
553 self.open_document(uri.as_str(), content);
555
556 let (diag_outputs, diag_effects) = self.refresh_diagnostics();
558 outputs.extend(diag_outputs);
559 effects.extend(diag_effects);
560 }
561 }
562 DidChangeTextDocument::METHOD => {
563 if let Ok(params) = serde_json::from_value::<DidChangeTextDocumentParams>(params) {
564 let uri = params.text_document.uri;
565 if let Some(change) = params.content_changes.into_iter().next() {
567 let content = change.text;
568
569 self.change_document(uri.as_str(), content);
571
572 let (diag_outputs, diag_effects) = self.refresh_diagnostics();
574 outputs.extend(diag_outputs);
575 effects.extend(diag_effects);
576 }
577 }
578 }
579 DidCloseTextDocument::METHOD => {
580 if let Ok(params) = serde_json::from_value::<DidCloseTextDocumentParams>(params) {
581 let uri = params.text_document.uri;
582 let uri_str = uri.as_str();
583
584 self.close_document(uri_str);
586
587 self.pending_requests
589 .retain(|_, pending| text_file_to_uri(pending.command.file()) != uri_str);
590
591 let (diag_outputs, diag_effects) = self.refresh_diagnostics();
593 outputs.extend(diag_outputs);
594 effects.extend(diag_effects);
595 }
596 }
597 "$/cancelRequest" => {
598 if let Some(id) = params.get("id") {
599 let core_id = CoreRequestId::from(id);
600 self.cancel_request(&core_id);
601 }
602 }
603 "initialized" | "exit" => {
604 }
606 _ => {
607 }
609 }
610
611 (outputs, effects)
612 }
613
614 fn refresh_diagnostics(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
621 let mut outputs = Vec::new();
622 let mut effects = Vec::new();
623
624 debug!("[LspCore] refresh_diagnostics");
625
626 let all_files = match self.runtime.poll(CollectDiagnosticTargets::new()) {
628 Ok(polled) => match polled.value {
629 Ok(files) => files,
630 Err(e) => {
631 error!("CollectDiagnosticTargets error: {}", e);
632 return (outputs, effects);
633 }
634 },
635 Err(QueryError::Suspend { .. }) => {
636 debug!("[LspCore] CollectDiagnosticTargets suspended");
637 let (new_effects, _) = self.collect_pending_assets();
638 effects.extend(new_effects);
639 return (outputs, effects);
640 }
641 Err(e) => {
642 Self::handle_query_error("CollectDiagnosticTargets", e);
643 return (outputs, effects);
644 }
645 };
646
647 debug!("[LspCore] diagnostic targets: {} files", all_files.len());
648
649 let mut current_uris = HashSet::new();
651 for file in all_files.iter() {
652 let query = LspFileDiagnostics::new(file.clone());
653
654 let last_revision = self
656 .diagnostics_subscriptions
657 .get(file)
658 .map(|s| s.last_revision)
659 .unwrap_or_default();
660
661 match self.runtime.poll(query.clone()) {
662 Ok(polled) => {
663 let uri = text_file_to_uri(file);
664 current_uris.insert(uri.clone());
665
666 if polled.revision != last_revision {
668 self.diagnostics_subscriptions.insert(
670 file.clone(),
671 FileDiagnosticsSubscription {
672 file: file.clone(),
673 query,
674 last_revision: polled.revision,
675 },
676 );
677
678 match polled.value {
679 Ok(diagnostics) => {
680 debug!(
681 "[LspCore] sending {} diagnostics for {}",
682 diagnostics.len(),
683 uri
684 );
685 if let Ok(parsed_uri) = uri.parse::<lsp_types::Uri>() {
686 let params = PublishDiagnosticsParams {
687 uri: parsed_uri,
688 diagnostics: diagnostics.as_ref().clone(),
689 version: None,
690 };
691 outputs.push(LspOutput::Notification {
692 method: PublishDiagnostics::METHOD.to_string(),
693 params: serde_json::to_value(params).unwrap(),
694 });
695 }
696 }
697 Err(e) => {
698 error!("Diagnostics query error for {}: {}", uri, e);
699 if let Ok(parsed_uri) = uri.parse::<lsp_types::Uri>() {
700 let params = PublishDiagnosticsParams {
701 uri: parsed_uri,
702 diagnostics: vec![],
703 version: None,
704 };
705 outputs.push(LspOutput::Notification {
706 method: PublishDiagnostics::METHOD.to_string(),
707 params: serde_json::to_value(params).unwrap(),
708 });
709 }
710 }
711 }
712 }
713 }
714 Err(QueryError::Suspend { .. }) => {
715 debug!("[LspCore] diagnostics for {:?} suspended", file);
716 self.diagnostics_subscriptions.insert(
718 file.clone(),
719 FileDiagnosticsSubscription {
720 file: file.clone(),
721 query,
722 last_revision,
723 },
724 );
725 let (new_effects, _) = self.collect_pending_assets();
726 effects.extend(new_effects);
727 }
728 Err(e) => {
729 Self::handle_query_error(&format!("LspFileDiagnostics({:?})", file), e);
730 }
731 }
732 }
733
734 let stale: Vec<_> = self
736 .published_uris
737 .difference(¤t_uris)
738 .cloned()
739 .collect();
740 for uri in stale {
741 debug!("[LspCore] clearing stale diagnostics for {}", uri);
742 if let Ok(parsed_uri) = uri.parse::<lsp_types::Uri>() {
743 let params = PublishDiagnosticsParams {
744 uri: parsed_uri,
745 diagnostics: vec![],
746 version: None,
747 };
748 outputs.push(LspOutput::Notification {
749 method: PublishDiagnostics::METHOD.to_string(),
750 params: serde_json::to_value(params).unwrap(),
751 });
752 }
753 }
754 self.published_uris = current_uris;
755
756 self.diagnostics_subscriptions
758 .retain(|f, _| all_files.contains(f));
759
760 (outputs, effects)
761 }
762
763 pub fn resolve_file(
769 &mut self,
770 file: TextFile,
771 content: Result<String, String>,
772 ) -> (Vec<LspOutput>, Vec<Effect>) {
773 match content {
775 Ok(text) => {
776 self.runtime.resolve_asset(
777 file.clone(),
778 TextFileContent(text),
779 DurabilityLevel::Volatile,
780 );
781 }
782 Err(error) => {
783 self.runtime.resolve_asset_error::<TextFile>(
784 file.clone(),
785 anyhow::anyhow!("{}", error),
786 DurabilityLevel::Volatile,
787 );
788 }
789 }
790 self.pending_assets.remove(&file);
791
792 self.process_after_asset_change()
794 }
795
796 pub fn resolve_glob(
800 &mut self,
801 id: &str,
802 files: Vec<TextFile>,
803 ) -> (Vec<LspOutput>, Vec<Effect>) {
804 if let Some(glob_key) = self.pending_globs.remove(id) {
805 self.runtime
806 .resolve_asset(glob_key, GlobResult(files), DurabilityLevel::Volatile);
807 }
808
809 self.process_after_asset_change()
811 }
812
813 fn process_after_asset_change(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
815 let mut outputs = Vec::new();
816 let mut effects = Vec::new();
817
818 let (req_outputs, req_effects) = self.retry_pending_requests();
820 outputs.extend(req_outputs);
821 effects.extend(req_effects);
822
823 let (diag_outputs, diag_effects) = self.check_diagnostics_subscriptions();
825 outputs.extend(diag_outputs);
826 effects.extend(diag_effects);
827
828 (outputs, effects)
829 }
830
831 fn retry_pending_requests(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
833 let mut outputs = Vec::new();
834 let mut effects = Vec::new();
835
836 let request_ids: Vec<CoreRequestId> = self.pending_requests.keys().cloned().collect();
837 let mut completed_ids = Vec::new();
838
839 for id in request_ids {
840 if let Some(pending) = self.pending_requests.get(&id) {
841 let command = pending.command.clone();
842
843 match self.try_execute(&command) {
844 Ok(result) => {
845 let json = self.result_to_value(result);
846 outputs.push(LspOutput::Response {
847 id: id.clone(),
848 result: Ok(json),
849 });
850 completed_ids.push(id);
851 }
852 Err(QueryError::Suspend { .. }) => {
853 let (new_effects, _) = self.collect_pending_assets();
855 effects.extend(new_effects);
856 }
857 Err(e) => {
858 if let Some(lsp_err) = Self::handle_query_error("RetryQuery", e) {
859 outputs.push(LspOutput::Response {
860 id: id.clone(),
861 result: Err(lsp_err),
862 });
863 completed_ids.push(id);
864 }
865 }
866 }
867 }
868 }
869
870 for id in completed_ids {
871 self.pending_requests.remove(&id);
872 }
873
874 (outputs, effects)
875 }
876
877 fn check_diagnostics_subscriptions(&mut self) -> (Vec<LspOutput>, Vec<Effect>) {
881 self.refresh_diagnostics()
882 }
883
884 fn handle_query_error(context: &str, err: QueryError) -> Option<LspError> {
889 match err {
890 QueryError::Suspend { .. } => None,
891 QueryError::Cancelled => {
892 error!("{}: query unexpectedly cancelled", context);
893 Some(LspError::internal_error("Query cancelled"))
894 }
895 QueryError::DependenciesRemoved { missing_keys } => {
896 error!("{}: dependencies removed: {:?}", context, missing_keys);
897 Some(LspError::internal_error("Dependencies removed"))
898 }
899 QueryError::Cycle { path } => {
900 error!("{}: query cycle: {:?}", context, path);
901 Some(LspError::internal_error(format!("Query cycle: {:?}", path)))
902 }
903 QueryError::InconsistentAssetResolution => {
904 unreachable!("InconsistentAssetResolution should not occur")
905 }
906 QueryError::UserError(e) => {
907 error!("{}: unexpected user error: {}", context, e);
908 Some(LspError::internal_error(e.to_string()))
909 }
910 }
911 }
912
913 fn try_execute(&mut self, command: &CommandQuery) -> Result<CommandResult, QueryError> {
915 match command {
916 CommandQuery::SemanticTokensFull(query) => {
917 let result = self.runtime.query(query.clone())?;
918 Ok(CommandResult::SemanticTokens(Some((*result).clone())))
919 }
920 CommandQuery::Completion(request) => {
921 let items = lsp_completion(&self.runtime, &request.file, request.offset)?;
922 Ok(CommandResult::Completion(items))
923 }
924 CommandQuery::Definition(request) => {
925 let source = self.runtime.asset(request.file.clone())?;
926 let offset = position_to_offset(source.get(), request.position) as u32;
927 Ok(CommandResult::Definition(lsp_definition(
928 &self.runtime,
929 &request.file,
930 offset,
931 )?))
932 }
933 CommandQuery::SchemaContent(file) => Ok(CommandResult::SchemaContent(
934 self.runtime.asset(file.clone())?.get().to_string(),
935 )),
936 CommandQuery::Hover(request) => {
937 let hover = lsp_hover(&self.runtime, &request.file, request.offset)?;
938 Ok(CommandResult::Hover(hover))
939 }
940 }
941 }
942
943 fn result_to_value(&self, result: CommandResult) -> Value {
945 match result {
946 CommandResult::SemanticTokens(tokens) => {
947 serde_json::to_value(tokens).unwrap_or(Value::Null)
948 }
949 CommandResult::Completion(items) => {
950 serde_json::to_value(CompletionResponse::Array(items)).unwrap_or(Value::Null)
951 }
952 CommandResult::SchemaContent(content) => Value::String(content),
953 CommandResult::Definition(links) => {
954 let response = if self.definition_link_support {
955 lsp_types::GotoDefinitionResponse::Link(links)
956 } else {
957 lsp_types::GotoDefinitionResponse::Array(
958 links
959 .into_iter()
960 .map(|link| lsp_types::Location {
961 uri: link.target_uri,
962 range: link.target_selection_range,
963 })
964 .collect(),
965 )
966 };
967 serde_json::to_value(response).expect("definition locations are JSON serializable")
968 }
969 CommandResult::Hover(hover) => serde_json::to_value(hover).unwrap_or(Value::Null),
970 }
971 }
972
973 fn collect_pending_assets(&mut self) -> (Vec<Effect>, HashSet<TextFile>) {
975 let mut effects = Vec::new();
976 let mut waiting_for = HashSet::new();
977
978 for pending in self.runtime.pending_assets() {
979 if let Some(file) = pending.key::<TextFile>() {
980 if !self.pending_assets.contains(file) {
981 self.pending_assets.insert(file.clone());
982 effects.push(Effect::FetchFile(file.clone()));
983 }
984 waiting_for.insert(file.clone());
985 } else if let Some(glob_key) = pending.key::<Glob>() {
986 let id = format!(
988 "{}:{}",
989 glob_key.base_dir.to_string_lossy(),
990 glob_key.pattern
991 );
992 if !self.pending_globs.contains_key(&id) {
993 self.pending_globs.insert(id.clone(), glob_key.clone());
994 effects.push(Effect::ExpandGlob {
995 id,
996 glob: glob_key.clone(),
997 });
998 }
999 }
1000 }
1001
1002 (effects, waiting_for)
1003 }
1004}
1005
1006impl Default for LspCore {
1007 fn default() -> Self {
1008 Self::new()
1009 }
1010}