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