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