1use std::sync::Arc;
2
3use tower_lsp::jsonrpc::Result;
4use tower_lsp::lsp_types::*;
5use tower_lsp::{LanguageServer, async_trait};
6
7use super::panic_guard::{guard_async, guard_async_result};
8use crate::completion::{CompletionCtx, filtered_completions_at};
9use crate::document::ast::ParsedDoc;
10use crate::document::open_files::compute_open_file_diagnostics;
11use crate::hover::{
12 class_hover_from_index, docs_for_symbol_from_index, extract_static_class_before_cursor,
13 hover_info_with_maps, method_hover_from_index, signature_for_symbol_from_index,
14};
15use crate::index::file_index::ClassKind;
16use crate::index::workspace_scan::{scan_workspace, send_refresh_requests};
17use crate::lang::config::LspConfig;
18use crate::navigation::symbols::{
19 document_symbols, resolve_workspace_symbol, workspace_symbols_from_workspace,
20};
21use crate::text::word_at_position;
22
23use crate::navigation::call_hierarchy::{
24 incoming_calls, outgoing_calls_indexed, prepare_call_hierarchy_indexed,
25};
26use crate::navigation::declaration::{goto_declaration, goto_declaration_from_index};
27use crate::navigation::implementation::{
28 find_implementations, find_implementations_mir_backed, find_method_implementations_mir_backed,
29};
30use crate::navigation::moniker::moniker_at;
31use crate::navigation::type_definition::{goto_type_definition, goto_type_definition_from_index};
32use crate::navigation::type_hierarchy::{
33 prepare_type_hierarchy_from_workspace, subtypes_of_mir_backed, supertypes_of_from_workspace,
34};
35
36use crate::analysis::code_lens::code_lenses;
37use crate::analysis::diagnostics::{diagnostics_from_doc, parse_document, parse_document_no_diags};
38use crate::analysis::document_highlight::document_highlights;
39use crate::analysis::inlay_hints::inlay_hints;
40use crate::analysis::inline_value::inline_values_in_range;
41use crate::analysis::semantic_tokens::{
42 compute_token_delta, semantic_tokens, semantic_tokens_range, token_hash,
43};
44
45use crate::editing::document_link::document_links;
46use crate::editing::folding::folding_ranges;
47use crate::editing::formatting::{format_document, format_range};
48use crate::editing::on_type_format::on_type_format;
49use crate::editing::rename::{prepare_rename, rename, rename_property, rename_variable};
50use crate::editing::selection_range::selection_ranges;
51use crate::editing::signature_help::signature_help;
52
53use super::helpers::{
54 cursor_is_on_property_decl, is_after_arrow, promoted_property_at_cursor, run_phpunit,
55};
56use super::{Backend, publish_with_dependents};
57
58#[async_trait]
59impl LanguageServer for Backend {
60 async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
61 self.handle_initialize(params).await
62 }
63
64 async fn initialized(&self, _params: InitializedParams) {
65 guard_async("initialized", async move {
66 self.handle_initialized(_params).await
67 })
68 .await
69 }
70
71 async fn did_change_configuration(&self, _params: DidChangeConfigurationParams) {
72 guard_async("did_change_configuration", async move {
73 let items = vec![ConfigurationItem {
76 scope_uri: None,
77 section: Some("php-lsp".to_string()),
78 }];
79 if let Ok(values) = self.client.configuration(items).await
80 && let Some(value) = values.into_iter().next()
81 {
82 let roots = self.root_paths.load_full();
83
84 let file_cfg = crate::lang::autoload::load_project_config_json(&roots);
87
88 if let Some(ver) = value.get("phpVersion").and_then(|v| v.as_str())
89 && !crate::lang::autoload::is_valid_php_version(ver)
90 {
91 self.client
92 .log_message(
93 tower_lsp::lsp_types::MessageType::WARNING,
94 format!(
95 "php-lsp: unsupported phpVersion {ver:?} — valid values: {}",
96 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
97 ),
98 )
99 .await;
100 }
101
102 let file_obj = file_cfg.as_ref().filter(|v| v.is_object());
103 let merged = LspConfig::merge_project_configs(file_obj, Some(&value));
104 let mut cfg = LspConfig::from_value(&merged);
105
106 let (ver, source) = self.resolve_php_version(cfg.php_version.as_deref());
108 self.client
109 .log_message(
110 tower_lsp::lsp_types::MessageType::INFO,
111 format!("php-lsp: using PHP {ver} ({source})"),
112 )
113 .await;
114 let ver = if source != "set by editor"
116 && !crate::lang::autoload::is_valid_php_version(&ver)
117 {
118 let clamped = crate::lang::autoload::clamp_php_version(&ver);
119 self.client
120 .show_message(
121 tower_lsp::lsp_types::MessageType::WARNING,
122 format!(
123 "php-lsp: detected PHP {ver} is outside the supported range ({}); \
124 using PHP {clamped} for analysis",
125 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
126 ),
127 )
128 .await;
129 clamped.to_string()
130 } else {
131 ver
132 };
133 cfg.php_version = Some(ver.clone());
134 if let Ok(pv) = ver.parse::<mir_analyzer::PhpVersion>() {
135 self.docs.set_php_version(pv);
136 }
137 self.config.store(Arc::new(cfg));
138 send_refresh_requests(&self.client).await;
139 }
140 })
141 .await
142 }
143
144 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
145 guard_async("did_change_workspace_folders", async move {
146 {
148 let mut roots = (**self.root_paths.load()).clone();
149 for removed in ¶ms.event.removed {
150 if let Ok(path) = removed.uri.to_file_path() {
151 roots.retain(|r| r != &path);
152 }
153 }
154 self.root_paths.store(Arc::new(roots));
155 }
156
157 let (exclude_paths, include_paths, max_indexed_files, cache_path) = {
159 let cfg = self.config.load();
160 (
161 cfg.exclude_paths.clone(),
162 cfg.include_paths.clone(),
163 cfg.max_indexed_files,
164 cfg.cache_path.clone(),
165 )
166 };
167 for added in ¶ms.event.added {
168 if let Ok(path) = added.uri.to_file_path() {
169 let is_new = {
170 let mut roots = (**self.root_paths.load()).clone();
171 if !roots.contains(&path) {
172 roots.push(path.clone());
173 self.root_paths.store(Arc::new(roots));
174 true
175 } else {
176 false
177 }
178 };
179 if is_new {
180 let docs = Arc::clone(&self.docs);
181 let open_files = self.open_files.clone();
182 let ex = exclude_paths.clone();
183 let ip = include_paths.clone();
184 let path_clone = path.clone();
185 let client = self.client.clone();
186 let cp = cache_path.clone();
187 tokio::spawn(async move {
188 let cache = if let Some(p) = cp {
189 Some(crate::index::cache::WorkspaceCache::with_dir(p))
190 } else {
191 crate::index::cache::WorkspaceCache::new(&path_clone)
192 };
193 scan_workspace(
194 path_clone,
195 docs,
196 open_files,
197 cache,
198 &ex,
199 &ip,
200 max_indexed_files,
201 None,
202 )
203 .await;
204 send_refresh_requests(&client).await;
205 });
206 }
207 }
208 }
209 })
210 .await
211 }
212
213 async fn shutdown(&self) -> Result<()> {
214 Ok(())
215 }
216
217 #[tracing::instrument(skip_all)]
218 async fn did_open(&self, params: DidOpenTextDocumentParams) {
219 guard_async("did_open", async move {
220 let uri = params.text_document.uri;
221 let text = params.text_document.text;
222
223 self.set_open_text(uri.clone(), text);
227
228 let parse_diags = self
231 .docs
232 .get_doc_salsa(&uri)
233 .map(|doc| diagnostics_from_doc(&doc))
234 .unwrap_or_default();
235 self.set_parse_diagnostics(&uri, parse_diags);
236
237 publish_with_dependents(
238 self.client.clone(),
239 Arc::clone(&self.docs),
240 self.open_files.clone(),
241 uri,
242 self.config.load().diagnostics.clone(),
243 )
244 .await;
245 })
246 .await
247 }
248
249 #[tracing::instrument(skip_all)]
250 async fn did_change(&self, params: DidChangeTextDocumentParams) {
251 guard_async("did_change", async move {
252 let uri = params.text_document.uri;
253 let mut updated: Option<String> = None;
258 for change in params.content_changes {
259 match change.range {
260 None => updated = Some(change.text),
261 Some(range) => {
262 let mut cur = match updated.take() {
263 Some(t) => t,
264 None => self.get_open_text(&uri).unwrap_or_default(),
265 };
266 crate::text::apply_content_change(&mut cur, range, &change.text);
267 updated = Some(cur);
268 }
269 }
270 }
271 let Some(text) = updated else { return };
272
273 let version = self.set_open_text(uri.clone(), text.clone());
277
278 let docs = Arc::clone(&self.docs);
279 let open_files = self.open_files.clone();
280 let client = self.client.clone();
281 let cfg = self.config.load();
282 let diag_cfg = cfg.diagnostics.clone();
283 let debounce_ms = cfg.debounce_ms;
284 tokio::spawn(async move {
285 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
288
289 if open_files.current_version(&uri) != Some(version) {
293 return;
294 }
295
296 let (_doc, parse_diags) =
297 tokio::task::spawn_blocking(move || parse_document(&text))
298 .await
299 .unwrap_or_else(|e| {
300 tracing::warn!("parse_document panicked for {uri}: {e}");
301 (ParsedDoc::default(), vec![])
302 });
303
304 if open_files.current_version(&uri) == Some(version) {
306 open_files.set_parse_diagnostics(&uri, parse_diags);
307 publish_with_dependents(client, docs, open_files, uri, diag_cfg).await;
308 }
309 });
310 })
311 .await
312 }
313
314 async fn did_close(&self, params: DidCloseTextDocumentParams) {
315 guard_async("did_close", async move {
316 let uri = params.text_document.uri;
317 self.close_open_file(&uri);
318 let disk_text = match uri.to_file_path() {
323 Ok(path) => tokio::fs::read_to_string(&path).await.ok(),
324 Err(_) => None,
325 };
326 if !self.open_files.contains(&uri) {
330 match disk_text {
331 Some(text) => self.docs.ingest(uri.clone(), &text),
332 None => self.docs.remove(&uri),
333 }
334 }
335 self.client.publish_diagnostics(uri, vec![], None).await;
337 })
338 .await
339 }
340
341 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
342
343 async fn will_save_wait_until(
344 &self,
345 params: WillSaveTextDocumentParams,
346 ) -> Result<Option<Vec<TextEdit>>> {
347 guard_async_result("will_save_wait_until", async move {
348 let source = self
349 .get_open_text(¶ms.text_document.uri)
350 .unwrap_or_default();
351 Ok(format_document(&source))
352 })
353 .await
354 }
355
356 async fn did_save(&self, params: DidSaveTextDocumentParams) {
357 guard_async("did_save", async move {
358 let uri = params.text_document.uri;
359 let diag_cfg = self.config.load().diagnostics.clone();
365 let all = compute_open_file_diagnostics(&self.docs, &self.open_files, &uri, &diag_cfg);
366 self.client
367 .publish_diagnostics(uri.clone(), all, None)
368 .await;
369
370 let cache_path = self.config.load().cache_path.clone();
375 if let Ok(path) = uri.to_file_path() {
376 let root = self.root_paths.load().first().cloned();
377 tokio::task::spawn_blocking(move || {
378 let cache = if let Some(p) = cache_path {
379 crate::index::cache::WorkspaceCache::with_dir(p)
380 } else if let Some(r) = root {
381 let Some(c) = crate::index::cache::WorkspaceCache::new(&r) else {
382 return;
383 };
384 c
385 } else {
386 return;
387 };
388 let Ok(text) = std::fs::read_to_string(&path) else {
389 return;
390 };
391 let key = crate::index::cache::WorkspaceCache::key_for(uri.as_str(), &text);
392 let doc = parse_document_no_diags(&text);
393 let index = crate::index::file_index::FileIndex::extract(&doc);
394 let _ = cache.write(&key, &index);
395 });
396 }
397 })
398 .await
399 }
400
401 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
402 guard_async("did_change_watched_files", async move {
403 for change in params.changes {
404 match change.typ {
405 FileChangeType::CREATED | FileChangeType::CHANGED => {
406 if let Ok(path) = change.uri.to_file_path()
407 && let Ok(text) = tokio::fs::read_to_string(&path).await
408 {
409 let doc = parse_document_no_diags(&text);
414 self.ingest_from_doc_if_not_open(change.uri.clone(), &doc);
415 }
416 }
417 FileChangeType::DELETED => {
418 self.docs.remove(&change.uri);
419 }
420 _ => {}
421 }
422 }
423 send_refresh_requests(&self.client).await;
425 })
426 .await
427 }
428
429 #[tracing::instrument(skip_all)]
430 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
431 guard_async_result("completion", async move {
432 let uri = ¶ms.text_document_position.text_document.uri;
433 let position = params.text_document_position.position;
434 let source = self.get_open_text(uri).unwrap_or_default();
435 let doc = match self.get_doc(uri) {
436 Some(d) => d,
437 None => return Ok(Some(CompletionResponse::Array(vec![]))),
438 };
439 let other_docs: Vec<Arc<ParsedDoc>> = self
440 .docs
441 .other_docs(uri, &self.open_urls())
442 .into_iter()
443 .map(|(_, d)| d)
444 .collect();
445 let trigger = params
447 .context
448 .as_ref()
449 .and_then(|c| c.trigger_character.clone());
450 let meta_arc = self.meta.load_full();
453 let imports = self.file_imports(uri);
454 let wi = self.workspace_index_async().await;
455 let wi_for_class_search = Arc::clone(&wi);
456 let uri_for_class_search = uri.clone();
457 let docs_for_lookup = Arc::clone(&self.docs);
458 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
459 let cr = *wi.classes_by_name.get(name)?.first()?;
460 let (uri, _) = wi.at(cr)?;
461 docs_for_lookup.get_doc_salsa(uri)
462 };
463 const WORKSPACE_CLASS_SEARCH_LIMIT: usize = 50;
474 let workspace_class_search_fn =
475 move |prefix: &str| -> Vec<(String, CompletionItemKind, String)> {
476 let prefix_lc = prefix.to_lowercase();
477 let table = &wi_for_class_search.classes_by_lowercase_name;
478 let start =
479 table.partition_point(|(name, _)| name.as_ref() < prefix_lc.as_str());
480 let mut out = Vec::new();
481 for (name, cr) in &table[start..] {
482 if !name.starts_with(prefix_lc.as_str()) {
483 break;
484 }
485 let Some((file_uri, cls)) = wi_for_class_search.at(*cr) else {
486 continue;
487 };
488 if *file_uri == uri_for_class_search {
489 continue;
490 }
491 let kind = match cls.kind {
492 ClassKind::Class | ClassKind::Trait => CompletionItemKind::CLASS,
493 ClassKind::Interface => CompletionItemKind::INTERFACE,
494 ClassKind::Enum => CompletionItemKind::ENUM,
495 };
496 out.push((cls.name.to_string(), kind, cls.fqn.to_string()));
497 if out.len() >= WORKSPACE_CLASS_SEARCH_LIMIT {
498 break;
499 }
500 }
501 out
502 };
503 let analysis = self.cached_analysis_async(uri).await;
504 let docs_for_tm = Arc::clone(&self.docs);
508 let doc_for_tm = Arc::clone(&doc);
509 let uri_for_tm = uri.clone();
510 let meta_arc_for_tm = Arc::clone(&meta_arc);
511 let get_type_map = move || {
512 let meta = if meta_arc_for_tm.is_empty() {
513 None
514 } else {
515 Some(&*meta_arc_for_tm)
516 };
517 docs_for_tm.cached_type_map(&uri_for_tm, &doc_for_tm, meta)
518 };
519 let session = self
520 .docs
521 .analysis_session(self.docs.workspace_php_version());
522 let uri_owned = uri.clone();
523 let uri_str = uri.to_string();
524 let items = match tokio::task::spawn_blocking(move || {
528 let meta_opt = if meta_arc.is_empty() {
529 None
530 } else {
531 Some(&*meta_arc)
532 };
533 let ctx = CompletionCtx {
534 source: Some(&source),
535 position: Some(position),
536 meta: meta_opt,
537 doc_uri: Some(&uri_owned),
538 file_imports: Some(&imports),
539 find_class_doc: Some(&find_class_doc_fn),
540 workspace_class_search: Some(&workspace_class_search_fn),
541 analysis: analysis.as_deref(),
542 type_map: Some(&get_type_map),
543 session: Some(session),
544 };
545 filtered_completions_at(&doc, &other_docs, trigger.as_deref(), &ctx)
546 })
547 .await
548 {
549 Ok(items) => items,
550 Err(e) => {
551 tracing::warn!("completion panicked for {uri_str}: {e}");
552 vec![]
553 }
554 };
555 Ok(Some(CompletionResponse::Array(items)))
556 })
557 .await
558 }
559
560 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
561 guard_async_result("completion_resolve", async move {
562 if item.documentation.is_some() && item.detail.is_some() {
563 return Ok(item);
564 }
565 let name = item.label.trim_end_matches(':');
567 let all_indexes = self.docs.all_indexes();
568 if item.detail.is_none()
569 && let Some(sig) = signature_for_symbol_from_index(name, &all_indexes)
570 {
571 item.detail = Some(sig);
572 }
573 if item.documentation.is_none()
574 && let Some(md) = docs_for_symbol_from_index(name, &all_indexes)
575 {
576 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
577 kind: MarkupKind::Markdown,
578 value: md,
579 }));
580 }
581 Ok(item)
582 })
583 .await
584 }
585
586 async fn goto_definition(
587 &self,
588 params: GotoDefinitionParams,
589 ) -> Result<Option<GotoDefinitionResponse>> {
590 guard_async_result("goto_definition", async move {
591 self.handle_goto_definition(params).await
592 })
593 .await
594 }
595
596 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
597 guard_async_result(
598 "references",
599 async move { self.handle_references(params).await },
600 )
601 .await
602 }
603
604 async fn prepare_rename(
605 &self,
606 params: TextDocumentPositionParams,
607 ) -> Result<Option<PrepareRenameResponse>> {
608 guard_async_result("prepare_rename", async move {
609 let uri = ¶ms.text_document.uri;
610 let source = self.get_open_text(uri).unwrap_or_default();
611 Ok(prepare_rename(&source, params.position).map(PrepareRenameResponse::Range))
612 })
613 .await
614 }
615
616 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
617 guard_async_result("rename", async move {
618 let uri = ¶ms.text_document_position.text_document.uri;
619 let position = params.text_document_position.position;
620 let source = self.get_open_text(uri).unwrap_or_default();
621 let word = match word_at_position(&source, position) {
622 Some(w) => w,
623 None => return Ok(None),
624 };
625 if word.starts_with('$') {
626 let doc = match self.get_doc(uri) {
627 Some(d) => d,
628 None => return Ok(None),
629 };
630 let prop_name = cursor_is_on_property_decl(&source, &doc.program().stmts, position)
634 .or_else(|| {
635 promoted_property_at_cursor(&source, &doc.program().stmts, position)
636 });
637 if let Some(prop_name) = prop_name {
638 let all_docs = self.docs.all_docs_for_scan();
639 return Ok(Some(rename_property(
640 &prop_name,
641 ¶ms.new_name,
642 &all_docs,
643 )));
644 }
645 Ok(Some(rename_variable(
646 &word,
647 ¶ms.new_name,
648 uri,
649 &doc,
650 position,
651 )))
652 } else if is_after_arrow(&source, position) {
653 let all_docs = self.docs.all_docs_for_scan();
654 Ok(Some(rename_property(&word, ¶ms.new_name, &all_docs)))
655 } else {
656 let all_docs = self.docs.all_docs_for_scan();
657 let doc_opt = self.get_doc(uri);
658 let target_fqn: Option<String> = doc_opt.as_ref().map(|doc| {
659 let imports = self.file_imports(uri);
660 crate::navigation::moniker::resolve_fqn(doc, &word, &imports)
661 });
662 Ok(Some(rename(
663 &word,
664 ¶ms.new_name,
665 &all_docs,
666 target_fqn.as_deref(),
667 )))
668 }
669 })
670 .await
671 }
672
673 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
674 guard_async_result("signature_help", async move {
675 let uri = ¶ms.text_document_position_params.text_document.uri;
676 let position = params.text_document_position_params.position;
677 let source = self.get_open_text(uri).unwrap_or_default();
678 let doc = match self.get_doc(uri) {
679 Some(d) => d,
680 None => return Ok(None),
681 };
682 let all_indexes = self.docs.all_indexes();
683 Ok(signature_help(&source, &doc, position, &all_indexes))
684 })
685 .await
686 }
687
688 #[tracing::instrument(skip_all)]
689 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
690 guard_async_result("hover", async move {
691 let uri = ¶ms.text_document_position_params.text_document.uri;
692 let position = params.text_document_position_params.position;
693 let source = self.get_open_text(uri).unwrap_or_default();
694 let doc = match self.get_doc(uri) {
695 Some(d) => d,
696 None => return Ok(None),
697 };
698 let other_docs = self.docs.other_docs(uri, &self.open_urls());
699 let other_maps = self.docs.other_symbol_maps(uri, &self.open_urls());
700 let analysis = self.cached_analysis_async(uri).await;
701 let hover_session = self
702 .docs
703 .analysis_session(self.docs.workspace_php_version());
704 let source_clone = source.clone();
705 let doc_clone = Arc::clone(&doc);
706 let uri_str = uri.to_string();
707 let result = match tokio::task::spawn_blocking(move || {
708 hover_info_with_maps(
709 &source_clone,
710 &doc_clone,
711 analysis.as_deref(),
712 position,
713 &other_docs,
714 &other_maps,
715 Some(&hover_session),
716 )
717 })
718 .await
719 {
720 Ok(r) => r,
721 Err(e) => {
722 tracing::warn!("hover panicked for {uri_str}: {e}");
723 None
724 }
725 };
726 if result.is_some() {
727 return Ok(result);
728 }
729 if let Some(word) = crate::text::word_at_position(&source, position) {
734 let wi = self.workspace_index_async().await;
735 if let Some(h) = class_hover_from_index(&word, &wi.files) {
737 return Ok(Some(h));
738 }
739 if let Some(resolved) = crate::hover::resolve_use_alias(&doc.program().stmts, &word)
741 && let Some(h) = class_hover_from_index(&resolved, &wi.files)
742 {
743 return Ok(Some(h));
744 }
745 if let Some(line_text) = source.lines().nth(position.line as usize)
747 && let Some(class_token) =
748 extract_static_class_before_cursor(line_text, position.character as usize)
749 {
750 if let Some(h) = method_hover_from_index(&class_token, &word, &wi.files) {
751 return Ok(Some(h));
752 }
753 if let Some(resolved_class) =
754 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
755 && let Some(h) = method_hover_from_index(&resolved_class, &word, &wi.files)
756 {
757 return Ok(Some(h));
758 }
759 }
760 }
761 Ok(None)
762 })
763 .await
764 }
765
766 async fn document_symbol(
767 &self,
768 params: DocumentSymbolParams,
769 ) -> Result<Option<DocumentSymbolResponse>> {
770 guard_async_result("document_symbol", async move {
771 let uri = ¶ms.text_document.uri;
772 let doc = match self.get_doc(uri) {
773 Some(d) => d,
774 None => return Ok(None),
775 };
776 Ok(Some(DocumentSymbolResponse::Nested(document_symbols(
777 doc.source(),
778 &doc,
779 ))))
780 })
781 .await
782 }
783
784 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
785 guard_async_result("folding_range", async move {
786 let uri = ¶ms.text_document.uri;
787 let doc = match self.get_doc(uri) {
788 Some(d) => d,
789 None => return Ok(None),
790 };
791 let ranges = folding_ranges(doc.source(), &doc);
792 Ok(if ranges.is_empty() {
793 None
794 } else {
795 Some(ranges)
796 })
797 })
798 .await
799 }
800
801 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
802 guard_async_result("inlay_hint", async move {
803 let uri = ¶ms.text_document.uri;
804 let doc = match self.get_doc(uri) {
805 Some(d) => d,
806 None => return Ok(None),
807 };
808 let analysis = self.cached_analysis_async(uri).await;
809 let wi = self.workspace_index_async().await;
810 let uri_str = uri.to_string();
811 let hints = match tokio::task::spawn_blocking(move || {
812 inlay_hints(
813 doc.source(),
814 &doc,
815 analysis.as_deref(),
816 params.range,
817 &wi.files,
818 )
819 })
820 .await
821 {
822 Ok(h) => h,
823 Err(e) => {
824 tracing::warn!("inlay_hint panicked for {uri_str}: {e}");
825 Vec::new()
826 }
827 };
828 Ok(Some(hints))
829 })
830 .await
831 }
832
833 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
834 let fallback = item.clone();
835 let resolved = guard_async("inlay_hint_resolve", async move {
836 if item.tooltip.is_some() {
837 return Some(item);
838 }
839 let func_name = item
840 .data
841 .as_ref()
842 .and_then(|d| d.get("php_lsp_fn"))
843 .and_then(|v| v.as_str())
844 .map(str::to_string);
845 if let Some(name) = func_name {
846 let all_indexes = self.docs.all_indexes();
847 if let Some(md) = docs_for_symbol_from_index(&name, &all_indexes) {
848 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
849 kind: MarkupKind::Markdown,
850 value: md,
851 }));
852 }
853 }
854 Some(item)
855 })
856 .await;
857 Ok(resolved.unwrap_or(fallback))
858 }
859
860 async fn symbol(
861 &self,
862 params: WorkspaceSymbolParams,
863 ) -> Result<Option<Vec<SymbolInformation>>> {
864 guard_async_result("symbol", async move {
865 let wi = self.workspace_index_async().await;
869 let query = params.query;
870 let results = match tokio::task::spawn_blocking(move || {
871 workspace_symbols_from_workspace(&query, &wi)
872 })
873 .await
874 {
875 Ok(r) => r,
876 Err(e) => {
877 tracing::warn!("workspace/symbol panicked: {e}");
878 Vec::new()
879 }
880 };
881 Ok(Some(results))
882 })
883 .await
884 }
885
886 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
887 let docs = self.docs.docs_for(&self.open_urls());
889 let fallback = params.clone();
890 let resolved = guard_async("symbol_resolve", async move {
891 Some(resolve_workspace_symbol(params, &docs))
892 })
893 .await;
894 Ok(resolved.unwrap_or(fallback))
895 }
896
897 #[tracing::instrument(skip_all)]
898 async fn semantic_tokens_full(
899 &self,
900 params: SemanticTokensParams,
901 ) -> Result<Option<SemanticTokensResult>> {
902 guard_async_result("semantic_tokens_full", async move {
903 let uri = ¶ms.text_document.uri;
904 let doc = match self.get_doc(uri) {
905 Some(d) => d,
906 None => {
907 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
908 result_id: None,
909 data: vec![],
910 })));
911 }
912 };
913 let tokens = semantic_tokens(doc.source(), &doc);
914 let result_id = token_hash(&tokens);
915 let tokens_arc = Arc::new(tokens);
916 self.docs
917 .store_token_cache(uri, result_id.clone(), Arc::clone(&tokens_arc));
918 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
919 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
920 result_id: Some(result_id),
921 data,
922 })))
923 })
924 .await
925 }
926
927 async fn semantic_tokens_range(
928 &self,
929 params: SemanticTokensRangeParams,
930 ) -> Result<Option<SemanticTokensRangeResult>> {
931 guard_async_result("semantic_tokens_range", async move {
932 let uri = ¶ms.text_document.uri;
933 let doc = match self.get_doc(uri) {
934 Some(d) => d,
935 None => {
936 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
937 result_id: None,
938 data: vec![],
939 })));
940 }
941 };
942 let tokens = semantic_tokens_range(doc.source(), &doc, params.range);
943 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
944 result_id: None,
945 data: tokens,
946 })))
947 })
948 .await
949 }
950
951 async fn semantic_tokens_full_delta(
952 &self,
953 params: SemanticTokensDeltaParams,
954 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
955 guard_async_result("semantic_tokens_full_delta", async move {
956 let uri = ¶ms.text_document.uri;
957 let doc = match self.get_doc(uri) {
958 Some(d) => d,
959 None => return Ok(None),
960 };
961
962 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
963 let new_result_id = token_hash(&new_tokens);
964 let prev_id = ¶ms.previous_result_id;
965
966 let result = match self.docs.get_token_cache(uri, prev_id) {
967 Some(old_tokens) => {
968 let edits = compute_token_delta(&old_tokens, &new_tokens);
969 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
970 result_id: Some(new_result_id.clone()),
971 edits,
972 })
973 }
974 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
976 result_id: Some(new_result_id.clone()),
977 data: (*new_tokens).clone(),
978 }),
979 };
980
981 self.docs.store_token_cache(uri, new_result_id, new_tokens);
982 Ok(Some(result))
983 })
984 .await
985 }
986
987 async fn selection_range(
988 &self,
989 params: SelectionRangeParams,
990 ) -> Result<Option<Vec<SelectionRange>>> {
991 guard_async_result("selection_range", async move {
992 let uri = ¶ms.text_document.uri;
993 let doc = match self.get_doc(uri) {
994 Some(d) => d,
995 None => return Ok(None),
996 };
997 let ranges = selection_ranges(&doc, ¶ms.positions);
998 Ok(if ranges.is_empty() {
999 None
1000 } else {
1001 Some(ranges)
1002 })
1003 })
1004 .await
1005 }
1006
1007 async fn prepare_call_hierarchy(
1008 &self,
1009 params: CallHierarchyPrepareParams,
1010 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1011 guard_async_result("prepare_call_hierarchy", async move {
1012 let uri = ¶ms.text_document_position_params.text_document.uri;
1013 let position = params.text_document_position_params.position;
1014 let source = self.get_open_text(uri).unwrap_or_default();
1015 let word = match word_at_position(&source, position) {
1016 Some(w) => w,
1017 None => return Ok(None),
1018 };
1019 let wi = self.workspace_index_async().await;
1022 let docs = Arc::clone(&self.docs);
1023 let get_doc = move |u: &Url| docs.get_doc_salsa(u);
1024 Ok(prepare_call_hierarchy_indexed(&word, &wi, &get_doc).map(|item| vec![item]))
1025 })
1026 .await
1027 }
1028
1029 async fn incoming_calls(
1030 &self,
1031 params: CallHierarchyIncomingCallsParams,
1032 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1033 guard_async_result("incoming_calls", async move {
1034 let docs = Arc::clone(&self.docs);
1037 let item_uri = params.item.uri.to_string();
1038 let item = params.item;
1039 let calls = match tokio::task::spawn_blocking(move || {
1040 let all_docs = docs.all_docs_for_scan();
1041 incoming_calls(&item, &all_docs)
1042 })
1043 .await
1044 {
1045 Ok(c) => c,
1046 Err(e) => {
1047 tracing::warn!("incoming_calls panicked for {item_uri}: {e}");
1048 vec![]
1049 }
1050 };
1051 Ok(if calls.is_empty() { None } else { Some(calls) })
1052 })
1053 .await
1054 }
1055
1056 async fn outgoing_calls(
1057 &self,
1058 params: CallHierarchyOutgoingCallsParams,
1059 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1060 guard_async_result("outgoing_calls", async move {
1061 let wi = self.workspace_index_async().await;
1064 let docs = Arc::clone(&self.docs);
1065 let item_uri = params.item.uri.to_string();
1066 let item = params.item;
1067 let calls = match tokio::task::spawn_blocking(move || {
1068 let get_doc = |u: &Url| docs.get_doc_salsa(u);
1069 outgoing_calls_indexed(&item, &wi, &get_doc)
1070 })
1071 .await
1072 {
1073 Ok(c) => c,
1074 Err(e) => {
1075 tracing::warn!("outgoing_calls panicked for {item_uri}: {e}");
1076 vec![]
1077 }
1078 };
1079 Ok(if calls.is_empty() { None } else { Some(calls) })
1080 })
1081 .await
1082 }
1083
1084 async fn document_highlight(
1085 &self,
1086 params: DocumentHighlightParams,
1087 ) -> Result<Option<Vec<DocumentHighlight>>> {
1088 guard_async_result("document_highlight", async move {
1089 let uri = ¶ms.text_document_position_params.text_document.uri;
1090 let position = params.text_document_position_params.position;
1091 let source = self.get_open_text(uri).unwrap_or_default();
1092 let doc = match self.get_doc_stale(uri) {
1095 Some(d) => d,
1096 None => return Ok(None),
1097 };
1098 let uri_str = uri.to_string();
1099 let highlights = match tokio::task::spawn_blocking(move || {
1101 document_highlights(&source, &doc, position)
1102 })
1103 .await
1104 {
1105 Ok(h) => h,
1106 Err(e) => {
1107 tracing::warn!("document_highlight panicked for {uri_str}: {e}");
1108 Vec::new()
1109 }
1110 };
1111 Ok(if highlights.is_empty() {
1112 None
1113 } else {
1114 Some(highlights)
1115 })
1116 })
1117 .await
1118 }
1119
1120 async fn linked_editing_range(
1121 &self,
1122 params: LinkedEditingRangeParams,
1123 ) -> Result<Option<LinkedEditingRanges>> {
1124 guard_async_result("linked_editing_range", async move {
1125 self.handle_linked_editing_range(params).await
1126 })
1127 .await
1128 }
1129
1130 async fn goto_implementation(
1131 &self,
1132 params: tower_lsp::lsp_types::request::GotoImplementationParams,
1133 ) -> Result<Option<tower_lsp::lsp_types::request::GotoImplementationResponse>> {
1134 guard_async_result("goto_implementation", async move {
1135 let uri = ¶ms.text_document_position_params.text_document.uri;
1136 let position = params.text_document_position_params.position;
1137 let source = self.get_open_text(uri).unwrap_or_default();
1138 let imports = self.file_imports(uri);
1139 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
1140 let (word, fqn_owned): (String, Option<String>) = if raw_word.contains('\\') {
1145 let short = raw_word
1146 .rsplit('\\')
1147 .next()
1148 .unwrap_or(&raw_word)
1149 .to_string();
1150 let full = raw_word.trim_start_matches('\\').to_string();
1151 (short, Some(full))
1152 } else {
1153 let fqn = imports.get(&raw_word).cloned();
1154 (raw_word, fqn)
1155 };
1156 let fqn = fqn_owned.as_deref();
1157 let open_docs = self.docs.docs_for(&self.open_urls());
1159 let mut locs = find_implementations(&word, fqn, &open_docs);
1160 let wi = self.workspace_index_async().await;
1163 if locs.is_empty() {
1164 let fqn_for_mir = fqn.unwrap_or(&word).to_string();
1165 let subtype_urls = self.docs.class_subtype_urls(&fqn_for_mir);
1166 locs = find_implementations_mir_backed(&word, fqn, &wi, &subtype_urls);
1167 }
1168 if locs.is_empty()
1172 && let Some(doc) = self.get_doc(uri)
1173 && let Some(enclosing) =
1174 crate::types::type_map::enclosing_class_at(&source, &doc, position)
1175 {
1176 let enclosing_fqn = wi
1178 .classes_by_name
1179 .get(&enclosing)
1180 .and_then(|refs| refs.first())
1181 .and_then(|r| wi.at(*r))
1182 .map(|(_, cls)| cls.fqn.as_ref().to_string());
1183 let method_subtype_urls = enclosing_fqn
1184 .as_deref()
1185 .map(|f| self.docs.class_subtype_urls(f))
1186 .unwrap_or_default();
1187 locs = find_method_implementations_mir_backed(
1188 &word,
1189 &enclosing,
1190 &wi,
1191 &method_subtype_urls,
1192 );
1193 }
1194 if locs.is_empty() {
1195 Ok(None)
1196 } else {
1197 Ok(Some(GotoDefinitionResponse::Array(locs)))
1198 }
1199 })
1200 .await
1201 }
1202
1203 async fn goto_declaration(
1204 &self,
1205 params: tower_lsp::lsp_types::request::GotoDeclarationParams,
1206 ) -> Result<Option<tower_lsp::lsp_types::request::GotoDeclarationResponse>> {
1207 guard_async_result("goto_declaration", async move {
1208 let uri = ¶ms.text_document_position_params.text_document.uri;
1209 let position = params.text_document_position_params.position;
1210 let source = self.get_open_text(uri).unwrap_or_default();
1211 let open_docs = self.docs.docs_for(&self.open_urls());
1213 if let Some(loc) = goto_declaration(&source, &open_docs, position) {
1214 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1215 }
1216 let all_indexes = self.docs.all_indexes();
1218 Ok(goto_declaration_from_index(&source, &all_indexes, position)
1219 .map(GotoDefinitionResponse::Scalar))
1220 })
1221 .await
1222 }
1223
1224 async fn goto_type_definition(
1225 &self,
1226 params: tower_lsp::lsp_types::request::GotoTypeDefinitionParams,
1227 ) -> Result<Option<tower_lsp::lsp_types::request::GotoTypeDefinitionResponse>> {
1228 guard_async_result("goto_type_definition", async move {
1229 let uri = ¶ms.text_document_position_params.text_document.uri;
1230 let position = params.text_document_position_params.position;
1231 let source = self.get_open_text(uri).unwrap_or_default();
1232 let doc = match self.get_doc(uri) {
1233 Some(d) => d,
1234 None => return Ok(None),
1235 };
1236 let analysis = self.cached_analysis_async(uri).await;
1237 let open_docs = self.docs.docs_for(&self.open_urls());
1239 let mut results =
1240 goto_type_definition(&source, &doc, analysis.as_deref(), &open_docs, position);
1241
1242 if results.is_empty() {
1244 let all_indexes = self.docs.all_indexes();
1245 results = goto_type_definition_from_index(
1246 &source,
1247 &doc,
1248 analysis.as_deref(),
1249 &all_indexes,
1250 position,
1251 );
1252 }
1253
1254 let response = match results.len() {
1256 0 => None,
1257 1 => Some(GotoDefinitionResponse::Scalar(
1258 results.into_iter().next().unwrap(),
1259 )),
1260 _ => Some(GotoDefinitionResponse::Array(results)),
1261 };
1262 Ok(response)
1263 })
1264 .await
1265 }
1266
1267 async fn prepare_type_hierarchy(
1268 &self,
1269 params: TypeHierarchyPrepareParams,
1270 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1271 guard_async_result("prepare_type_hierarchy", async move {
1272 let uri = ¶ms.text_document_position_params.text_document.uri;
1273 let position = params.text_document_position_params.position;
1274 let source = self.get_open_text(uri).unwrap_or_default();
1275 let wi = self.workspace_index_async().await;
1277 Ok(
1278 prepare_type_hierarchy_from_workspace(&source, &wi, position)
1279 .map(|item| vec![item]),
1280 )
1281 })
1282 .await
1283 }
1284
1285 async fn supertypes(
1286 &self,
1287 params: TypeHierarchySupertypesParams,
1288 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1289 guard_async_result("supertypes", async move {
1290 let wi = self.workspace_index_async().await;
1294 let loaded_new = self
1295 .ensure_direct_supertypes_loaded(¶ms.item.name, &wi)
1296 .await;
1297 let wi = if loaded_new {
1298 self.workspace_index_async().await
1299 } else {
1300 wi
1301 };
1302 let result = supertypes_of_from_workspace(¶ms.item, &wi);
1303 Ok(if result.is_empty() {
1304 None
1305 } else {
1306 Some(result)
1307 })
1308 })
1309 .await
1310 }
1311
1312 async fn subtypes(
1313 &self,
1314 params: TypeHierarchySubtypesParams,
1315 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1316 guard_async_result("subtypes", async move {
1317 let wi = self.workspace_index_async().await;
1318 let item_fqn = wi
1320 .classes_by_name
1321 .get(¶ms.item.name)
1322 .and_then(|refs| refs.first())
1323 .and_then(|r| wi.at(*r))
1324 .map(|(_, cls)| cls.fqn.as_ref().to_string());
1325 let subtype_urls = item_fqn
1326 .as_deref()
1327 .map(|f| self.docs.class_subtype_urls(f))
1328 .unwrap_or_default();
1329 let result =
1330 subtypes_of_mir_backed(¶ms.item, item_fqn.as_deref(), &wi, &subtype_urls);
1331 Ok(if result.is_empty() {
1332 None
1333 } else {
1334 Some(result)
1335 })
1336 })
1337 .await
1338 }
1339
1340 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
1341 guard_async_result("code_lens", async move {
1342 let uri = ¶ms.text_document.uri;
1343 let doc = match self.get_doc(uri) {
1344 Some(d) => d,
1345 None => return Ok(None),
1346 };
1347 let docs = Arc::clone(&self.docs);
1352 let cancel_rev = self.docs.write_rev();
1353 let docs_cancel = Arc::clone(&self.docs);
1354 let uri_owned = uri.clone();
1355 let uri_str = uri.to_string();
1356 let lenses = match tokio::task::spawn_blocking(move || {
1357 let all_docs = docs.all_docs_for_scan();
1358 code_lenses(&uri_owned, &doc, &all_docs, move || {
1359 docs_cancel.write_rev() != cancel_rev
1360 })
1361 })
1362 .await
1363 {
1364 Ok(l) => l,
1365 Err(e) => {
1366 tracing::warn!("code_lens panicked for {uri_str}: {e}");
1367 vec![]
1368 }
1369 };
1370 Ok(if lenses.is_empty() {
1371 None
1372 } else {
1373 Some(lenses)
1374 })
1375 })
1376 .await
1377 }
1378
1379 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
1380 let fallback = params.clone();
1381 let resolved = guard_async("code_lens_resolve", async move {
1382 Some(params)
1384 })
1385 .await;
1386 Ok(resolved.unwrap_or(fallback))
1387 }
1388
1389 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
1390 guard_async_result("document_link", async move {
1391 let uri = ¶ms.text_document.uri;
1392 let doc = match self.get_doc(uri) {
1393 Some(d) => d,
1394 None => return Ok(None),
1395 };
1396 let links = document_links(uri, &doc, doc.source());
1397 Ok(if links.is_empty() { None } else { Some(links) })
1398 })
1399 .await
1400 }
1401
1402 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
1403 let fallback = params.clone();
1404 let resolved = guard_async("document_link_resolve", async move {
1405 Some(params)
1407 })
1408 .await;
1409 Ok(resolved.unwrap_or(fallback))
1410 }
1411
1412 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
1413 guard_async_result("formatting", async move {
1414 let uri = ¶ms.text_document.uri;
1415 let source = self.get_open_text(uri).unwrap_or_default();
1416 Ok(format_document(&source))
1417 })
1418 .await
1419 }
1420
1421 async fn range_formatting(
1422 &self,
1423 params: DocumentRangeFormattingParams,
1424 ) -> Result<Option<Vec<TextEdit>>> {
1425 guard_async_result("range_formatting", async move {
1426 let uri = ¶ms.text_document.uri;
1427 let source = self.get_open_text(uri).unwrap_or_default();
1428 Ok(format_range(&source, params.range))
1429 })
1430 .await
1431 }
1432
1433 async fn on_type_formatting(
1434 &self,
1435 params: DocumentOnTypeFormattingParams,
1436 ) -> Result<Option<Vec<TextEdit>>> {
1437 guard_async_result("on_type_formatting", async move {
1438 let uri = ¶ms.text_document_position.text_document.uri;
1439 let source = self.get_open_text(uri).unwrap_or_default();
1440 let edits = on_type_format(
1441 &source,
1442 params.text_document_position.position,
1443 ¶ms.ch,
1444 ¶ms.options,
1445 );
1446 Ok(if edits.is_empty() { None } else { Some(edits) })
1447 })
1448 .await
1449 }
1450
1451 async fn execute_command(
1452 &self,
1453 params: ExecuteCommandParams,
1454 ) -> Result<Option<serde_json::Value>> {
1455 guard_async_result("execute_command", async move {
1456 match params.command.as_str() {
1457 "php-lsp.runTest" => {
1458 let file_uri = params
1460 .arguments
1461 .first()
1462 .and_then(|v| v.as_str())
1463 .and_then(|s| Url::parse(s).ok());
1464 let filter = params
1465 .arguments
1466 .get(1)
1467 .and_then(|v| v.as_str())
1468 .unwrap_or("")
1469 .to_string();
1470
1471 let root = self.root_paths.load().first().cloned();
1472 let client = self.client.clone();
1473
1474 tokio::spawn(async move {
1475 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
1476 });
1477
1478 Ok(None)
1479 }
1480 _ => Ok(None),
1481 }
1482 })
1483 .await
1484 }
1485
1486 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
1487 guard_async_result("will_rename_files", async move {
1488 self.handle_will_rename_files(params).await
1489 })
1490 .await
1491 }
1492
1493 async fn did_rename_files(&self, params: RenameFilesParams) {
1494 guard_async("did_rename_files", async move {
1495 self.handle_did_rename_files(params).await
1496 })
1497 .await
1498 }
1499
1500 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
1501 guard_async_result("will_create_files", async move {
1502 self.handle_will_create_files(params).await
1503 })
1504 .await
1505 }
1506
1507 async fn did_create_files(&self, params: CreateFilesParams) {
1508 guard_async("did_create_files", async move {
1509 self.handle_did_create_files(params).await
1510 })
1511 .await
1512 }
1513
1514 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
1517 guard_async_result("will_delete_files", async move {
1518 self.handle_will_delete_files(params).await
1519 })
1520 .await
1521 }
1522
1523 async fn did_delete_files(&self, params: DeleteFilesParams) {
1524 guard_async("did_delete_files", async move {
1525 self.handle_did_delete_files(params).await
1526 })
1527 .await
1528 }
1529
1530 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
1531 guard_async_result("moniker", async move {
1532 let uri = ¶ms.text_document_position_params.text_document.uri;
1533 let position = params.text_document_position_params.position;
1534 let source = self.get_open_text(uri).unwrap_or_default();
1535 let doc = match self.get_doc(uri) {
1536 Some(d) => d,
1537 None => return Ok(None),
1538 };
1539 let imports = self.file_imports(uri);
1540 Ok(moniker_at(&source, &doc, position, &imports).map(|m| vec![m]))
1541 })
1542 .await
1543 }
1544
1545 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
1546 guard_async_result("inline_value", async move {
1547 let uri = ¶ms.text_document.uri;
1548 let source = self.get_open_text(uri).unwrap_or_default();
1549 let values = inline_values_in_range(&source, params.range);
1550 Ok(if values.is_empty() {
1551 None
1552 } else {
1553 Some(values)
1554 })
1555 })
1556 .await
1557 }
1558
1559 async fn diagnostic(
1560 &self,
1561 params: DocumentDiagnosticParams,
1562 ) -> Result<DocumentDiagnosticReportResult> {
1563 let report = guard_async("diagnostic", async move {
1564 Some(self.handle_diagnostic(params).await)
1565 })
1566 .await;
1567 match report {
1568 Some(r) => r,
1569 None => Ok(DocumentDiagnosticReportResult::Report(
1570 DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
1571 )),
1572 }
1573 }
1574
1575 async fn workspace_diagnostic(
1576 &self,
1577 params: WorkspaceDiagnosticParams,
1578 ) -> Result<WorkspaceDiagnosticReportResult> {
1579 let report = guard_async("workspace_diagnostic", async move {
1580 Some(self.handle_workspace_diagnostic(params).await)
1581 })
1582 .await;
1583 match report {
1584 Some(r) => r,
1585 None => Ok(WorkspaceDiagnosticReportResult::Report(
1586 WorkspaceDiagnosticReport::default(),
1587 )),
1588 }
1589 }
1590
1591 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
1592 guard_async_result("code_action", async move {
1593 self.handle_code_action(params).await
1594 })
1595 .await
1596 }
1597
1598 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
1599 guard_async_result("code_action_resolve", async move {
1600 self.handle_code_action_resolve(item).await
1601 })
1602 .await
1603 }
1604}