1use std::sync::Arc;
2
3use tower_lsp_server::LanguageServer;
4use tower_lsp_server::jsonrpc::Result;
5use tower_lsp_server::ls_types::*;
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_workspace_index, docs_for_symbol_from_index,
13 docs_for_symbol_from_index_scoped, extract_static_class_before_cursor, hover_info_with_maps,
14 method_hover_from_workspace_index, 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::lang::is_bare_keyword_at;
20use crate::navigation::symbols::{
21 document_symbols, resolve_workspace_symbol, workspace_symbols_from_workspace,
22};
23use crate::text::word_at_position;
24
25use crate::navigation::call_hierarchy::{
26 incoming_calls_indexed, outgoing_calls_indexed, prepare_call_hierarchy_indexed,
27};
28use crate::navigation::declaration::{goto_declaration, goto_declaration_from_index};
29use crate::navigation::moniker::moniker_at;
30use crate::navigation::type_definition::{
31 goto_type_definition_exact, goto_type_definition_from_index_exact,
32 goto_type_definition_from_index_short_name_fallback, goto_type_definition_short_name_fallback,
33};
34use crate::navigation::type_hierarchy::{
35 prepare_type_hierarchy_from_workspace, subtypes_of_mir_backed, supertypes_of_from_workspace,
36};
37
38use crate::analysis::code_lens::code_lenses;
39use crate::analysis::diagnostics::{diagnostics_from_doc, parse_document, parse_document_no_diags};
40use crate::analysis::document_highlight::document_highlights;
41use crate::analysis::inlay_hints::inlay_hints;
42use crate::analysis::inline_value::inline_values_in_range;
43use crate::analysis::semantic_tokens::{
44 compute_token_delta, semantic_tokens, semantic_tokens_range, token_hash,
45};
46
47use crate::editing::document_link::document_links;
48use crate::editing::folding::folding_ranges;
49use crate::editing::formatting::{format_document, format_range};
50use crate::editing::on_type_format::on_type_format;
51use crate::editing::rename::{prepare_rename, rename_variable};
52use crate::editing::selection_range::selection_ranges;
53use crate::editing::signature_help::signature_help;
54
55use super::helpers::{
56 cursor_is_on_method_decl, cursor_is_on_property_decl, promoted_property_at_cursor, run_phpunit,
57};
58use super::{Backend, publish_with_dependents};
59
60const WARM_RESWEEP_IDLE_MS: u64 = 1_000;
64
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_server::ls_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_server::ls_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_server::ls_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 let version_changed = ver
141 .parse::<mir_analyzer::PhpVersion>()
142 .is_ok_and(|pv| self.docs.set_php_version(pv));
143 self.config.store(Arc::new(cfg));
144 if version_changed {
145 self.remirror_open_files();
153 self.rescan_roots_after_version_change(&roots).await;
154 }
155 send_refresh_requests(&self.client).await;
156 }
157 })
158 .await
159 }
160
161 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
162 guard_async("did_change_workspace_folders", async move {
163 {
165 let mut roots = (**self.root_paths.load()).clone();
166 for removed in ¶ms.event.removed {
167 if let Some(path) = removed.uri.to_file_path() {
168 roots.retain(|r| r.as_path() != path.as_ref());
169 }
170 }
171 self.root_paths.store(Arc::new(roots));
172 }
173
174 let (exclude_paths, include_paths, max_indexed_files, cache_path) = {
176 let cfg = self.config.load();
177 let mut exclude = cfg.exclude_paths.clone();
178 if !cfg.index_vendor && !exclude.iter().any(|p| p == "vendor" || p == "vendor/") {
179 exclude.push("vendor/".to_string());
180 }
181 (
182 exclude,
183 cfg.include_paths.clone(),
184 cfg.max_indexed_files,
185 cfg.cache_path.clone(),
186 )
187 };
188 for added in ¶ms.event.added {
189 if let Some(path) = added.uri.to_file_path().map(|c| c.into_owned()) {
190 let is_new = {
191 let mut roots = (**self.root_paths.load()).clone();
192 if !roots.contains(&path) {
193 roots.push(path.clone());
194 self.root_paths.store(Arc::new(roots));
195 true
196 } else {
197 false
198 }
199 };
200 if is_new {
201 let docs = Arc::clone(&self.docs);
202 let open_files = self.open_files.clone();
203 let ex = exclude_paths.clone();
204 let ip = include_paths.clone();
205 let path_clone = path.clone();
206 let client = self.client.clone();
207 let cp = cache_path.clone();
208 tokio::spawn(async move {
209 let cache = if let Some(p) = cp {
210 Some(crate::index::cache::WorkspaceCache::with_dir(p))
211 } else {
212 crate::index::cache::WorkspaceCache::new(&path_clone)
213 };
214 scan_workspace(
215 path_clone,
216 Arc::clone(&docs),
217 open_files,
218 cache,
219 &ex,
220 &ip,
221 max_indexed_files,
222 None,
223 )
224 .await;
225 super::offload::run("workspaceFolders.warmStart", move || {
230 docs.get_workspace_index_salsa();
231 docs.warm_start_indexes();
232 })
233 .await;
234 send_refresh_requests(&client).await;
235 });
236 }
237 }
238 }
239 })
240 .await
241 }
242
243 async fn shutdown(&self) -> Result<()> {
244 let docs = Arc::clone(&self.docs);
247 self.blocking("shutdown.flush", move || docs.flush_analysis_cache())
248 .await;
249 Ok(())
250 }
251
252 #[tracing::instrument(skip_all)]
253 async fn did_open(&self, params: DidOpenTextDocumentParams) {
254 guard_async("did_open", async move {
255 let uri = params.text_document.uri;
256 let text = params.text_document.text;
257
258 self.set_open_text(uri.clone(), text);
262
263 let docs = Arc::clone(&self.docs);
267 let uri_for_task = uri.clone();
268 let parse_diags = self
269 .blocking_gated(super::debug_gate::GATE_DID_OPEN_PARSE, move || {
270 docs.get_doc_salsa(&uri_for_task)
271 .map(|doc| diagnostics_from_doc(&doc))
272 .unwrap_or_default()
273 })
274 .await
275 .unwrap_or_default();
276 self.set_parse_diagnostics(&uri, parse_diags);
277
278 publish_with_dependents(
279 self.client.clone(),
280 Arc::clone(&self.docs),
281 self.open_files.clone(),
282 uri,
283 self.config.load().diagnostics.clone(),
284 self.laravel.load_full().is_laravel,
285 )
286 .await;
287 })
288 .await
289 }
290
291 #[tracing::instrument(skip_all)]
292 async fn did_change(&self, params: DidChangeTextDocumentParams) {
293 guard_async("did_change", async move {
294 let uri = params.text_document.uri;
295 let mut updated: Option<String> = None;
300 for change in params.content_changes {
301 match change.range {
302 None => updated = Some(change.text),
303 Some(range) => {
304 let mut cur = match updated.take() {
305 Some(t) => t,
306 None => self.get_open_text(&uri).unwrap_or_default().to_string(),
307 };
308 crate::text::apply_content_change(&mut cur, range, &change.text);
309 updated = Some(cur);
310 }
311 }
312 }
313 let Some(text) = updated else { return };
314
315 let version = self.set_open_text(uri.clone(), text.clone());
319
320 let docs = Arc::clone(&self.docs);
321 let open_files = self.open_files.clone();
322 let client = self.client.clone();
323 let cfg = self.config.load();
324 let diag_cfg = cfg.diagnostics.clone();
325 let debounce_ms = cfg.debounce_ms;
326 let warm_analysis = cfg.warm_analysis;
327 let is_laravel = self.laravel.load_full().is_laravel;
328 tokio::spawn(async move {
329 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
332
333 if open_files.current_version(&uri) != Some(version) {
337 return;
338 }
339
340 let (_doc, parse_diags) =
341 super::offload::run("did_change.parse", move || parse_document(&text))
342 .await
343 .unwrap_or_default();
344
345 if open_files.current_version(&uri) != Some(version) {
347 return;
348 }
349 open_files.set_parse_diagnostics(&uri, parse_diags);
350 publish_with_dependents(
351 client,
352 Arc::clone(&docs),
353 open_files.clone(),
354 uri.clone(),
355 diag_cfg,
356 is_laravel,
357 )
358 .await;
359
360 if warm_analysis && docs.is_index_ready() {
365 tokio::time::sleep(std::time::Duration::from_millis(WARM_RESWEEP_IDLE_MS))
366 .await;
367 if open_files.current_version(&uri) != Some(version) {
368 return;
369 }
370 let open_urls = open_files.urls();
371 drop(tokio::task::spawn_blocking(move || {
376 let cancel = docs.begin_warm_sweep();
377 docs.warm_analysis_sweep(&open_urls, &cancel);
378 }));
379 }
380 });
381 })
382 .await
383 }
384
385 async fn did_close(&self, params: DidCloseTextDocumentParams) {
386 guard_async("did_close", async move {
387 let uri = params.text_document.uri;
388 self.close_open_file(&uri);
389 let disk_text = match uri.to_file_path() {
394 Some(path) => tokio::fs::read_to_string(&path).await.ok(),
395 None => None,
396 };
397 if !self.open_files.contains(&uri) {
401 match disk_text {
402 Some(text) => self.docs.ingest(uri.clone(), &text),
403 None => self.docs.remove(&uri),
404 }
405 }
406 self.client.publish_diagnostics(uri, vec![], None).await;
408 })
409 .await
410 }
411
412 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
413
414 async fn will_save_wait_until(
415 &self,
416 params: WillSaveTextDocumentParams,
417 ) -> Result<Option<Vec<TextEdit>>> {
418 guard_async_result("will_save_wait_until", async move {
419 let source = self
420 .get_open_text(¶ms.text_document.uri)
421 .unwrap_or_default();
422 Ok(self
425 .blocking("willSaveWaitUntil.format", move || format_document(&source))
426 .await
427 .unwrap_or_default())
428 })
429 .await
430 }
431
432 async fn did_save(&self, params: DidSaveTextDocumentParams) {
433 guard_async("did_save", async move {
434 let uri = params.text_document.uri;
435 let diag_cfg = self.config.load().diagnostics.clone();
441 let is_laravel = self.laravel.load_full().is_laravel;
442 let docs = Arc::clone(&self.docs);
446 let open_files = self.open_files.clone();
447 let uri_for_diags = uri.clone();
448 let all = self
449 .blocking_gated(super::debug_gate::GATE_DID_SAVE_DIAGNOSTICS, move || {
450 compute_open_file_diagnostics(
451 &docs,
452 &open_files,
453 &uri_for_diags,
454 &diag_cfg,
455 is_laravel,
456 )
457 })
458 .await
459 .unwrap_or_default();
460 self.open_files
461 .note_published(&uri, super::diagnostics_content_hash(&all));
462 self.client
463 .publish_diagnostics(uri.clone(), all, None)
464 .await;
465
466 let cache_path = self.config.load().cache_path.clone();
471 if let Some(path) = uri.to_file_path().map(|c| c.into_owned()) {
472 let root = self.root_paths.load().first().cloned();
473 tokio::task::spawn_blocking({
477 let path = path.clone();
478 let uri = uri.clone();
479 let root = root.clone();
480 move || {
481 let cache = if let Some(p) = cache_path {
482 crate::index::cache::WorkspaceCache::with_dir(p)
483 } else if let Some(r) = &root {
484 let Some(c) = crate::index::cache::WorkspaceCache::new(r) else {
485 return;
486 };
487 c
488 } else {
489 return;
490 };
491 let Ok(text) = std::fs::read_to_string(&path) else {
492 return;
493 };
494 let key = crate::index::cache::WorkspaceCache::key_for(uri.as_str(), &text);
495 let doc = parse_document_no_diags(&text);
496 let index = crate::index::file_index::FileIndex::extract(&doc);
497 let _ = cache.write(&key, &index);
498 }
499 });
500
501 let ext_cfg = self.config.load().external_tools.clone();
509 if (ext_cfg.phpstan.enabled || ext_cfg.phpcs.enabled)
510 && let Some(version) = self.open_files.current_version(&uri)
511 {
512 let client = self.client.clone();
513 let docs = Arc::clone(&self.docs);
514 let open_files = self.open_files.clone();
515 let diag_cfg = self.config.load().diagnostics.clone();
516 let uri = uri.clone();
517 tokio::spawn(async move {
518 let diagnostics = crate::analysis::external::run_external_diagnostics(
519 &ext_cfg,
520 &path,
521 root.as_deref(),
522 )
523 .await;
524 if open_files.current_version(&uri) != Some(version) {
525 return;
526 }
527 open_files.set_external_diagnostics(&uri, version, diagnostics);
528 publish_with_dependents(
529 client, docs, open_files, uri, diag_cfg, is_laravel,
530 )
531 .await;
532 });
533 }
534 }
535 })
536 .await
537 }
538
539 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
540 guard_async("did_change_watched_files", async move {
541 enum Change {
542 Upsert(Uri, String),
543 Delete(Uri),
544 }
545 let mut changes = Vec::with_capacity(params.changes.len());
546 for change in params.changes {
547 match change.typ {
548 FileChangeType::CREATED | FileChangeType::CHANGED => {
549 if let Some(path) = change.uri.to_file_path()
550 && let Ok(text) = tokio::fs::read_to_string(&path).await
551 {
552 changes.push(Change::Upsert(change.uri.clone(), text));
553 }
554 }
555 FileChangeType::DELETED => changes.push(Change::Delete(change.uri.clone())),
556 _ => {}
557 }
558 }
559
560 let docs = Arc::clone(&self.docs);
565 let open_files = self.open_files.clone();
566 self.blocking_gated(
567 super::debug_gate::GATE_DID_CHANGE_WATCHED_FILES,
568 move || {
569 for change in changes {
570 match change {
571 Change::Upsert(uri, text) => {
572 let doc = parse_document_no_diags(&text);
577 if !open_files.contains(&uri) {
578 docs.ingest_from_doc(uri.clone(), &doc);
579 }
580 docs.note_new_file_declarations(&uri);
584 }
585 Change::Delete(uri) => docs.remove(&uri),
586 }
587 }
588 },
589 )
590 .await;
591 send_refresh_requests(&self.client).await;
593
594 if self.config.load().warm_analysis && self.docs.is_index_ready() {
598 let docs = Arc::clone(&self.docs);
599 let open_urls = self.open_files.urls();
600 drop(tokio::task::spawn_blocking(move || {
603 let cancel = docs.begin_warm_sweep();
604 docs.warm_analysis_sweep(&open_urls, &cancel);
605 }));
606 }
607 })
608 .await
609 }
610
611 #[tracing::instrument(skip_all)]
612 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
613 guard_async_result("completion", async move {
614 let uri = ¶ms.text_document_position.text_document.uri;
615 let position = params.text_document_position.position;
616 let source = self.get_open_text(uri).unwrap_or_default();
617 let doc = match self.get_doc(uri) {
618 Some(d) => d,
619 None => return Ok(Some(CompletionResponse::Array(vec![]))),
620 };
621 let other_docs: Vec<Arc<ParsedDoc>> = self
622 .docs
623 .other_docs(uri, &self.open_urls())
624 .into_iter()
625 .map(|(_, d)| d)
626 .collect();
627 let trigger = params
629 .context
630 .as_ref()
631 .and_then(|c| c.trigger_character.clone());
632 let laravel_arc = self.laravel.load_full();
633 let imports = self.file_imports(uri);
634 let wi = self.workspace_index_async().await;
635 let wi_for_class_search = Arc::clone(&wi);
636 let uri_for_class_search = uri.clone();
637 let docs_for_lookup = Arc::clone(&self.docs);
638 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
639 let cr = wi.resolve_class_ref(name)?;
640 let (uri, _) = wi.at(cr)?;
641 docs_for_lookup.get_doc_salsa(uri)
642 };
643 const WORKSPACE_CLASS_SEARCH_LIMIT: usize = 50;
654 let workspace_class_search_fn =
655 move |prefix: &str| -> Vec<(String, CompletionItemKind, String)> {
656 let prefix_lc = prefix.to_lowercase();
657 let table = &wi_for_class_search.classes_by_lowercase_name;
658 let start =
659 table.partition_point(|(name, _)| name.as_ref() < prefix_lc.as_str());
660 let mut out = Vec::new();
661 for (name, cr) in &table[start..] {
662 if !name.starts_with(prefix_lc.as_str()) {
663 break;
664 }
665 let Some((file_uri, cls)) = wi_for_class_search.at(*cr) else {
666 continue;
667 };
668 if *file_uri == uri_for_class_search {
669 continue;
670 }
671 let kind = match cls.kind {
672 ClassKind::Class | ClassKind::Trait => CompletionItemKind::CLASS,
673 ClassKind::Interface => CompletionItemKind::INTERFACE,
674 ClassKind::Enum => CompletionItemKind::ENUM,
675 };
676 out.push((cls.name.to_string(), kind, cls.fqn.to_string()));
677 if out.len() >= WORKSPACE_CLASS_SEARCH_LIMIT {
678 break;
679 }
680 }
681 out
682 };
683 let analysis = self.cached_analysis_async(uri).await;
684 let session = self.docs.current_analysis_session();
685 let uri_owned = uri.clone();
686 let items = self
690 .blocking("completion", move || {
691 let ctx = CompletionCtx {
692 source: Some(&source),
693 position: Some(position),
694 doc_uri: Some(&uri_owned),
695 file_imports: Some(&imports),
696 find_class_doc: Some(&find_class_doc_fn),
697 workspace_class_search: Some(&workspace_class_search_fn),
698 analysis: analysis.as_deref(),
699 session: Some(session),
700 laravel: Some(&laravel_arc),
701 };
702 filtered_completions_at(&doc, &other_docs, trigger.as_deref(), &ctx)
703 })
704 .await
705 .unwrap_or_default();
706 Ok(Some(CompletionResponse::Array(items)))
707 })
708 .await
709 }
710
711 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
712 guard_async_result("completion_resolve", async move {
713 if item.documentation.is_some() && item.detail.is_some() {
714 return Ok(item);
715 }
716 let name = item.label.trim_end_matches(':').to_string();
718 let class_hint = item
722 .data
723 .as_ref()
724 .and_then(|d| d.get("class"))
725 .and_then(|v| v.as_str())
726 .map(str::to_string);
727 let need_detail = item.detail.is_none();
728 let need_docs = item.documentation.is_none();
729 let docs = Arc::clone(&self.docs);
730 let (detail, documentation) = self
735 .blocking_gated(super::debug_gate::GATE_COMPLETION_RESOLVE, move || {
736 docs.with_all_indexes(|all_indexes| {
737 let detail = need_detail
738 .then(|| {
739 signature_for_symbol_from_index_scoped(
740 &name,
741 all_indexes,
742 class_hint.as_deref(),
743 )
744 })
745 .flatten();
746 let documentation = need_docs
747 .then(|| {
748 docs_for_symbol_from_index_scoped(
749 &name,
750 all_indexes,
751 class_hint.as_deref(),
752 )
753 })
754 .flatten();
755 (detail, documentation)
756 })
757 })
758 .await
759 .unwrap_or_default();
760 if let Some(sig) = detail {
761 item.detail = Some(sig);
762 }
763 if let Some(md) = documentation {
764 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
765 kind: MarkupKind::Markdown,
766 value: md,
767 }));
768 }
769 Ok(item)
770 })
771 .await
772 }
773
774 async fn goto_definition(
775 &self,
776 params: GotoDefinitionParams,
777 ) -> Result<Option<GotoDefinitionResponse>> {
778 guard_async_result("goto_definition", async move {
779 self.handle_goto_definition(params).await
780 })
781 .await
782 }
783
784 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
785 guard_async_result(
786 "references",
787 async move { self.handle_references(params).await },
788 )
789 .await
790 }
791
792 async fn prepare_rename(
793 &self,
794 params: TextDocumentPositionParams,
795 ) -> Result<Option<PrepareRenameResponse>> {
796 guard_async_result("prepare_rename", async move {
797 let uri = ¶ms.text_document.uri;
798 let doc = match self.get_doc(uri) {
799 Some(d) => d,
800 None => return Ok(None),
801 };
802 let position = params.position;
803 let range = self
807 .blocking_gated(super::debug_gate::GATE_PREPARE_RENAME, move || {
808 prepare_rename(&doc, position)
809 })
810 .await
811 .unwrap_or_default();
812 Ok(range.map(PrepareRenameResponse::Range))
813 })
814 .await
815 }
816
817 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
818 guard_async_result("rename", async move {
819 let uri = ¶ms.text_document_position.text_document.uri;
820 let position = params.text_document_position.position;
821 let source = self.get_open_text(uri).unwrap_or_default();
822 let word = match word_at_position(&source, position) {
823 Some(w) => w,
824 None => return Ok(None),
825 };
826 if word.starts_with('$') {
827 let doc = match self.get_doc(uri) {
828 Some(d) => d,
829 None => return Ok(None),
830 };
831 let word_task = word.clone();
838 let new_name = params.new_name.clone();
839 let uri_task = uri.clone();
840 let doc_task = Arc::clone(&doc);
841 let source_task = Arc::clone(&source);
842 let variable_edit = self
843 .blocking_gated(super::debug_gate::GATE_RENAME_VARIABLE, move || {
844 let on_property_decl = cursor_is_on_property_decl(
845 &source_task,
846 &doc_task.program().stmts,
847 position,
848 )
849 .is_some()
850 || promoted_property_at_cursor(
851 &source_task,
852 &doc_task.program().stmts,
853 position,
854 )
855 .is_some();
856 if on_property_decl {
857 None
858 } else {
859 Some(rename_variable(
860 &word_task, &new_name, &uri_task, &doc_task, position,
861 ))
862 }
863 })
864 .await
865 .flatten();
866 if let Some(edit) = variable_edit {
867 return Ok(Some(edit));
868 }
869 }
870 Ok(self.indexed_rename(uri, position, ¶ms.new_name).await)
874 })
875 .await
876 }
877
878 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
879 guard_async_result("signature_help", async move {
880 let uri = ¶ms.text_document_position_params.text_document.uri;
881 let position = params.text_document_position_params.position;
882 let source = self.get_open_text(uri).unwrap_or_default();
883 let doc = match self.get_doc(uri) {
884 Some(d) => d,
885 None => return Ok(None),
886 };
887 let index_data = self.docs.get_workspace_index_salsa();
888 let analysis = self.cached_analysis_async(uri).await;
889 let doc_clone = Arc::clone(&doc);
890 let result = self
891 .blocking("signature_help", move || {
892 signature_help(
893 &source,
894 &doc_clone,
895 position,
896 &index_data.files,
897 analysis.as_deref(),
898 )
899 })
900 .await
901 .flatten();
902 Ok(result)
903 })
904 .await
905 }
906
907 #[tracing::instrument(skip_all)]
908 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
909 guard_async_result("hover", async move {
910 let uri = ¶ms.text_document_position_params.text_document.uri;
911 let position = params.text_document_position_params.position;
912 let source = self.get_open_text(uri).unwrap_or_default();
913 let doc = match self.get_doc(uri) {
914 Some(d) => d,
915 None => return Ok(None),
916 };
917 let open = self.open_urls();
918 let other_docs = self.docs.other_docs(uri, &open);
919 let other_maps = self.docs.other_symbol_maps(uri, &open);
920 let analysis = self.cached_analysis_async(uri).await;
921 let hover_session = self.docs.current_analysis_session();
922 let source_clone = source.clone();
923 let doc_clone = Arc::clone(&doc);
924 let mut wi_cache: Option<Arc<crate::db::workspace_index::WorkspaceIndexData>> = None;
931 let wi = self.workspace_index_cached(&mut wi_cache).await;
932 let docs_for_lookup = Arc::clone(&self.docs);
933 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
934 let cr = wi.resolve_class_ref(name)?;
935 let (uri, _) = wi.at(cr)?;
936 docs_for_lookup.get_doc_salsa(uri)
937 };
938 let result = self
939 .blocking("hover", move || {
940 hover_info_with_maps(
941 &source_clone,
942 &doc_clone,
943 analysis.as_deref(),
944 position,
945 &other_docs,
946 &other_maps,
947 Some(&hover_session),
948 Some(&find_class_doc_fn),
949 )
950 })
951 .await
952 .flatten();
953 if result.is_some() {
954 return Ok(result);
955 }
956 if let Some(word) = crate::text::word_at_position(&source, position) {
961 let wi = self.workspace_index_cached(&mut wi_cache).await;
962 if let Some(h) = class_hover_from_workspace_index(&word, None, &wi) {
964 return Ok(Some(h));
965 }
966 if let Some((resolved, resolved_fqn)) =
970 crate::hover::resolve_use_alias_fqn(&doc.program().stmts, &word)
971 && let Some(h) =
972 class_hover_from_workspace_index(&resolved, Some(&resolved_fqn), &wi)
973 {
974 return Ok(Some(h));
975 }
976 if let Some(line_text) = source.lines().nth(position.line as usize)
978 && let Some(class_token) =
979 extract_static_class_before_cursor(line_text, position.character as usize)
980 {
981 if let Some(h) = method_hover_from_workspace_index(&class_token, &word, &wi) {
982 return Ok(Some(h));
983 }
984 if let Some(resolved_class) =
985 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
986 && let Some(h) =
987 method_hover_from_workspace_index(&resolved_class, &word, &wi)
988 {
989 return Ok(Some(h));
990 }
991 }
992 }
993 Ok(None)
994 })
995 .await
996 }
997
998 async fn document_symbol(
999 &self,
1000 params: DocumentSymbolParams,
1001 ) -> Result<Option<DocumentSymbolResponse>> {
1002 guard_async_result("document_symbol", async move {
1003 let uri = ¶ms.text_document.uri;
1004 let doc = match self.get_doc(uri) {
1005 Some(d) => d,
1006 None => return Ok(None),
1007 };
1008 let symbols = self
1010 .blocking_gated(super::debug_gate::GATE_DOCUMENT_SYMBOL, move || {
1011 document_symbols(doc.source(), &doc)
1012 })
1013 .await
1014 .unwrap_or_default();
1015 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1016 })
1017 .await
1018 }
1019
1020 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
1021 guard_async_result("folding_range", async move {
1022 let uri = ¶ms.text_document.uri;
1023 let doc = match self.get_doc(uri) {
1024 Some(d) => d,
1025 None => return Ok(None),
1026 };
1027 let ranges = self
1030 .blocking_gated(super::debug_gate::GATE_FOLDING_RANGE, move || {
1031 folding_ranges(doc.source(), &doc)
1032 })
1033 .await
1034 .unwrap_or_default();
1035 Ok(if ranges.is_empty() {
1036 None
1037 } else {
1038 Some(ranges)
1039 })
1040 })
1041 .await
1042 }
1043
1044 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
1045 guard_async_result("inlay_hint", async move {
1046 let uri = ¶ms.text_document.uri;
1047 let doc = match self.get_doc(uri) {
1048 Some(d) => d,
1049 None => return Ok(None),
1050 };
1051 let analysis = self.cached_analysis_async(uri).await;
1052 let wi = self.workspace_index_async().await;
1053 let hints = self
1054 .blocking("inlay_hint", move || {
1055 let workspace_defs = wi.func_signatures();
1059 inlay_hints(
1060 doc.source(),
1061 &doc,
1062 analysis.as_deref(),
1063 params.range,
1064 &workspace_defs,
1065 )
1066 })
1067 .await
1068 .unwrap_or_default();
1069 Ok(Some(hints))
1070 })
1071 .await
1072 }
1073
1074 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
1075 let fallback = item.clone();
1076 let resolved = guard_async("inlay_hint_resolve", async move {
1077 if item.tooltip.is_some() {
1078 return Some(item);
1079 }
1080 let func_name = item
1081 .data
1082 .as_ref()
1083 .and_then(|d| d.get("php_lsp_fn"))
1084 .and_then(|v| v.as_str())
1085 .map(str::to_string);
1086 if let Some(name) = func_name {
1087 let docs = Arc::clone(&self.docs);
1088 let tooltip = self
1091 .blocking_gated(super::debug_gate::GATE_INLAY_HINT_RESOLVE, move || {
1092 docs.with_all_indexes(|all_indexes| {
1093 docs_for_symbol_from_index(&name, all_indexes)
1094 })
1095 })
1096 .await
1097 .flatten();
1098 if let Some(md) = tooltip {
1099 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
1100 kind: MarkupKind::Markdown,
1101 value: md,
1102 }));
1103 }
1104 }
1105 Some(item)
1106 })
1107 .await;
1108 Ok(resolved.unwrap_or(fallback))
1109 }
1110
1111 async fn symbol(
1112 &self,
1113 params: WorkspaceSymbolParams,
1114 ) -> Result<Option<WorkspaceSymbolResponse>> {
1115 guard_async_result("symbol", async move {
1116 let wi = self.workspace_index_async().await;
1120 let query = params.query;
1121 let results = self
1122 .blocking("workspace/symbol", move || {
1123 workspace_symbols_from_workspace(&query, &wi)
1124 })
1125 .await
1126 .unwrap_or_default();
1127 Ok(Some(WorkspaceSymbolResponse::Flat(results)))
1128 })
1129 .await
1130 }
1131
1132 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
1133 let docs = self.docs.docs_for(&self.open_urls());
1135 let fallback = params.clone();
1136 let resolved = guard_async("symbol_resolve", async move {
1137 Some(resolve_workspace_symbol(params, &docs))
1138 })
1139 .await;
1140 Ok(resolved.unwrap_or(fallback))
1141 }
1142
1143 #[tracing::instrument(skip_all)]
1144 async fn semantic_tokens_full(
1145 &self,
1146 params: SemanticTokensParams,
1147 ) -> Result<Option<SemanticTokensResult>> {
1148 guard_async_result("semantic_tokens_full", async move {
1149 let uri = params.text_document.uri;
1150 let doc = match self.get_doc(&uri) {
1151 Some(d) => d,
1152 None => {
1153 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1154 result_id: None,
1155 data: vec![],
1156 })));
1157 }
1158 };
1159 let docs = Arc::clone(&self.docs);
1160 let uri_for_task = uri.clone();
1161 let (result_id, data) = self
1164 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_FULL, move || {
1165 let tokens = semantic_tokens(doc.source(), &doc);
1166 let result_id = token_hash(&tokens);
1167 let tokens_arc = Arc::new(tokens);
1168 docs.store_token_cache(
1169 &uri_for_task,
1170 result_id.clone(),
1171 Arc::clone(&tokens_arc),
1172 );
1173 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
1174 (result_id, data)
1175 })
1176 .await
1177 .unwrap_or_default();
1178 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1179 result_id: Some(result_id),
1180 data,
1181 })))
1182 })
1183 .await
1184 }
1185
1186 async fn semantic_tokens_range(
1187 &self,
1188 params: SemanticTokensRangeParams,
1189 ) -> Result<Option<SemanticTokensRangeResult>> {
1190 guard_async_result("semantic_tokens_range", async move {
1191 let uri = params.text_document.uri;
1192 let doc = match self.get_doc(&uri) {
1193 Some(d) => d,
1194 None => {
1195 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1196 result_id: None,
1197 data: vec![],
1198 })));
1199 }
1200 };
1201 let range = params.range;
1202 let tokens = self
1203 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_RANGE, move || {
1204 semantic_tokens_range(doc.source(), &doc, range)
1205 })
1206 .await
1207 .unwrap_or_default();
1208 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1209 result_id: None,
1210 data: tokens,
1211 })))
1212 })
1213 .await
1214 }
1215
1216 async fn semantic_tokens_full_delta(
1217 &self,
1218 params: SemanticTokensDeltaParams,
1219 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
1220 guard_async_result("semantic_tokens_full_delta", async move {
1221 let uri = params.text_document.uri;
1222 let doc = match self.get_doc(&uri) {
1223 Some(d) => d,
1224 None => return Ok(None),
1225 };
1226
1227 let docs = Arc::clone(&self.docs);
1228 let uri_for_task = uri.clone();
1229 let prev_id = params.previous_result_id;
1230 let result = self
1231 .blocking_gated(
1232 super::debug_gate::GATE_SEMANTIC_TOKENS_FULL_DELTA,
1233 move || {
1234 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
1235 let new_result_id = token_hash(&new_tokens);
1236
1237 let result = match docs.get_token_cache(&uri_for_task, &prev_id) {
1238 Some(old_tokens) => {
1239 let edits = compute_token_delta(&old_tokens, &new_tokens);
1240 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
1241 result_id: Some(new_result_id.clone()),
1242 edits,
1243 })
1244 }
1245 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
1247 result_id: Some(new_result_id.clone()),
1248 data: (*new_tokens).clone(),
1249 }),
1250 };
1251
1252 docs.store_token_cache(&uri_for_task, new_result_id, new_tokens);
1253 result
1254 },
1255 )
1256 .await;
1257 let Some(result) = result else {
1258 return Ok(None);
1259 };
1260 Ok(Some(result))
1261 })
1262 .await
1263 }
1264
1265 async fn selection_range(
1266 &self,
1267 params: SelectionRangeParams,
1268 ) -> Result<Option<Vec<SelectionRange>>> {
1269 guard_async_result("selection_range", async move {
1270 let uri = ¶ms.text_document.uri;
1271 let doc = match self.get_doc(uri) {
1272 Some(d) => d,
1273 None => return Ok(None),
1274 };
1275 let ranges = self
1279 .blocking_gated(super::debug_gate::GATE_SELECTION_RANGE, move || {
1280 selection_ranges(&doc, ¶ms.positions)
1281 })
1282 .await
1283 .unwrap_or_default();
1284 Ok(if ranges.is_empty() {
1285 None
1286 } else {
1287 Some(ranges)
1288 })
1289 })
1290 .await
1291 }
1292
1293 async fn prepare_call_hierarchy(
1294 &self,
1295 params: CallHierarchyPrepareParams,
1296 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1297 guard_async_result("prepare_call_hierarchy", async move {
1298 let uri = ¶ms.text_document_position_params.text_document.uri;
1299 let position = params.text_document_position_params.position;
1300 let source = self.get_open_text(uri).unwrap_or_default();
1301 let word = match word_at_position(&source, position) {
1302 Some(w) => w,
1303 None => return Ok(None),
1304 };
1305 if is_bare_keyword_at(&source, position, &word) {
1309 return Ok(None);
1310 }
1311 let wi = self.workspace_index_async().await;
1316 let docs = Arc::clone(&self.docs);
1317 let item = self
1318 .blocking_gated(super::debug_gate::GATE_PREPARE_CALL_HIERARCHY, move || {
1319 let get_doc = move |u: &Uri| docs.get_doc_salsa(u);
1320 prepare_call_hierarchy_indexed(&word, &wi, &get_doc)
1321 })
1322 .await
1323 .flatten();
1324 Ok(item.map(|item| vec![item]))
1325 })
1326 .await
1327 }
1328
1329 async fn incoming_calls(
1330 &self,
1331 params: CallHierarchyIncomingCallsParams,
1332 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1333 guard_async_result("incoming_calls", async move {
1334 let docs = Arc::clone(&self.docs);
1337 let item = params.item;
1338 let calls = self
1339 .blocking("incoming_calls", move || {
1340 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
1343 incoming_calls_indexed(&item, &docs, Some(cancel_rev))
1344 })
1345 .await
1346 .unwrap_or_default();
1347 Ok(if calls.is_empty() { None } else { Some(calls) })
1348 })
1349 .await
1350 }
1351
1352 async fn outgoing_calls(
1353 &self,
1354 params: CallHierarchyOutgoingCallsParams,
1355 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1356 guard_async_result("outgoing_calls", async move {
1357 let wi = self.workspace_index_async().await;
1360 let docs = Arc::clone(&self.docs);
1361 let item = params.item;
1362 let calls = self
1363 .blocking("outgoing_calls", move || {
1364 let get_doc = |u: &Uri| docs.get_doc_salsa(u);
1365 outgoing_calls_indexed(&item, &wi, &get_doc)
1366 })
1367 .await
1368 .unwrap_or_default();
1369 Ok(if calls.is_empty() { None } else { Some(calls) })
1370 })
1371 .await
1372 }
1373
1374 async fn document_highlight(
1375 &self,
1376 params: DocumentHighlightParams,
1377 ) -> Result<Option<Vec<DocumentHighlight>>> {
1378 guard_async_result("document_highlight", async move {
1379 let uri = ¶ms.text_document_position_params.text_document.uri;
1380 let position = params.text_document_position_params.position;
1381 let source = self.get_open_text(uri).unwrap_or_default();
1382 let doc = match self.get_doc_stale(uri) {
1385 Some(d) => d,
1386 None => return Ok(None),
1387 };
1388 let highlights = self
1390 .blocking("document_highlight", move || {
1391 document_highlights(&source, &doc, position)
1392 })
1393 .await
1394 .unwrap_or_default();
1395 Ok(if highlights.is_empty() {
1396 None
1397 } else {
1398 Some(highlights)
1399 })
1400 })
1401 .await
1402 }
1403
1404 async fn linked_editing_range(
1405 &self,
1406 params: LinkedEditingRangeParams,
1407 ) -> Result<Option<LinkedEditingRanges>> {
1408 guard_async_result("linked_editing_range", async move {
1409 self.handle_linked_editing_range(params).await
1410 })
1411 .await
1412 }
1413
1414 async fn goto_implementation(
1415 &self,
1416 params: tower_lsp_server::ls_types::request::GotoImplementationParams,
1417 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoImplementationResponse>> {
1418 guard_async_result("goto_implementation", async move {
1419 let uri = ¶ms.text_document_position_params.text_document.uri;
1420 let position = params.text_document_position_params.position;
1421 let source = self.get_open_text(uri).unwrap_or_default();
1422 let imports = self.file_imports(uri);
1423 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
1424 if raw_word.is_empty() || is_bare_keyword_at(&source, position, &raw_word) {
1425 return Ok(None);
1426 }
1427 let (word, fqn): (String, String) = if raw_word.contains('\\') {
1430 let short = raw_word
1431 .rsplit('\\')
1432 .next()
1433 .unwrap_or(&raw_word)
1434 .to_string();
1435 (short, raw_word.trim_start_matches('\\').to_string())
1436 } else {
1437 let resolved = match self.get_doc(uri) {
1441 Some(doc) => crate::navigation::moniker::resolve_fqn(&doc, &raw_word, &imports),
1442 None => raw_word.clone(),
1443 };
1444 (raw_word, resolved.trim_start_matches('\\').to_string())
1445 };
1446
1447 let doc_for_method_check = self.get_doc(uri);
1453 let on_method_decl = self
1454 .blocking_gated(super::debug_gate::GATE_GOTO_IMPLEMENTATION, move || {
1455 doc_for_method_check.is_some_and(|doc| {
1456 cursor_is_on_method_decl(doc.source(), &doc.program().stmts, position)
1457 })
1458 })
1459 .await
1460 .unwrap_or(false);
1461
1462 let mut locs: Vec<Location> = if on_method_decl {
1465 Vec::new()
1466 } else {
1467 let docs = Arc::clone(&self.docs);
1468 let fqn_task = fqn.clone();
1469 self.blocking("goto_implementation.subtypes", move || {
1470 docs.indexed_subtype_classes(&fqn_task, false)
1471 })
1472 .await
1473 .unwrap_or_default()
1474 .into_iter()
1475 .filter_map(|site| subtype_site_to_location(&site.file, &site.range))
1476 .collect()
1477 };
1478
1479 if locs.is_empty()
1482 && let Some(doc) = self.get_doc(uri)
1483 && let Some(enclosing) =
1484 crate::types::type_map::enclosing_class_at(&source, &doc, position)
1485 {
1486 let enclosing_fqn =
1487 crate::navigation::moniker::resolve_fqn(&doc, &enclosing, &imports)
1488 .trim_start_matches('\\')
1489 .to_string();
1490 let docs = Arc::clone(&self.docs);
1491 let method = word.clone();
1492 let impls = self
1493 .blocking("goto_implementation.method_impls", move || {
1494 docs.indexed_method_implementations(&enclosing_fqn, &method)
1495 })
1496 .await
1497 .unwrap_or_default();
1498 locs = impls
1499 .into_iter()
1500 .filter_map(|(_, file, range)| subtype_site_to_location(&file, &range))
1501 .collect();
1502 }
1503 if locs.is_empty() {
1504 Ok(None)
1505 } else {
1506 Ok(Some(GotoDefinitionResponse::Array(locs)))
1507 }
1508 })
1509 .await
1510 }
1511
1512 async fn goto_declaration(
1513 &self,
1514 params: tower_lsp_server::ls_types::request::GotoDeclarationParams,
1515 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoDeclarationResponse>> {
1516 guard_async_result("goto_declaration", async move {
1517 let uri = ¶ms.text_document_position_params.text_document.uri;
1518 let position = params.text_document_position_params.position;
1519 let source = self.get_open_text(uri).unwrap_or_default();
1520 let open_docs = self.docs.docs_for(&self.open_urls());
1526 let source_for_pass1 = source.clone();
1527 let first_pass = self
1528 .blocking("goto_declaration.open_docs", move || {
1529 goto_declaration(&source_for_pass1, &open_docs, position)
1530 })
1531 .await
1532 .unwrap_or_default();
1533 if let Some(loc) = first_pass {
1534 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1535 }
1536 let wi = self.workspace_index_async().await;
1539 let second_pass = self
1540 .blocking("goto_declaration.index", move || {
1541 goto_declaration_from_index(&source, &wi, position)
1542 })
1543 .await
1544 .unwrap_or_default();
1545 Ok(second_pass.map(GotoDefinitionResponse::Scalar))
1546 })
1547 .await
1548 }
1549
1550 async fn goto_type_definition(
1551 &self,
1552 params: tower_lsp_server::ls_types::request::GotoTypeDefinitionParams,
1553 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoTypeDefinitionResponse>> {
1554 guard_async_result("goto_type_definition", async move {
1555 let uri = ¶ms.text_document_position_params.text_document.uri;
1556 let position = params.text_document_position_params.position;
1557 let source = self.get_open_text(uri).unwrap_or_default();
1558 let doc = match self.get_doc(uri) {
1559 Some(d) => d,
1560 None => return Ok(None),
1561 };
1562 let analysis = self.cached_analysis_async(uri).await;
1563 let open_docs = self.docs.docs_for(&self.open_urls());
1564 let docs = Arc::clone(&self.docs);
1565
1566 let response = self
1571 .blocking_gated(super::debug_gate::GATE_GOTO_TYPE_DEFINITION, move || {
1572 let mut results = goto_type_definition_exact(
1577 &source,
1578 &doc,
1579 analysis.as_deref(),
1580 &open_docs,
1581 position,
1582 );
1583 if results.is_empty() {
1584 results = docs.with_all_indexes(|all_indexes| {
1585 goto_type_definition_from_index_exact(
1586 &source,
1587 &doc,
1588 analysis.as_deref(),
1589 all_indexes,
1590 position,
1591 )
1592 });
1593 }
1594 if results.is_empty() {
1595 results = goto_type_definition_short_name_fallback(
1596 &source,
1597 &doc,
1598 analysis.as_deref(),
1599 &open_docs,
1600 position,
1601 );
1602 }
1603 if results.is_empty() {
1604 results = docs.with_all_indexes(|all_indexes| {
1605 goto_type_definition_from_index_short_name_fallback(
1606 &source,
1607 &doc,
1608 analysis.as_deref(),
1609 all_indexes,
1610 position,
1611 )
1612 });
1613 }
1614
1615 match results.len() {
1617 0 => None,
1618 1 => Some(GotoDefinitionResponse::Scalar(
1619 results.into_iter().next().unwrap(),
1620 )),
1621 _ => Some(GotoDefinitionResponse::Array(results)),
1622 }
1623 })
1624 .await
1625 .unwrap_or_default();
1626 Ok(response)
1627 })
1628 .await
1629 }
1630
1631 async fn prepare_type_hierarchy(
1632 &self,
1633 params: TypeHierarchyPrepareParams,
1634 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1635 guard_async_result("prepare_type_hierarchy", async move {
1636 let uri = ¶ms.text_document_position_params.text_document.uri;
1637 let position = params.text_document_position_params.position;
1638 let source = self.get_open_text(uri).unwrap_or_default();
1639 let wi = self.workspace_index_async().await;
1641 Ok(
1642 prepare_type_hierarchy_from_workspace(&source, uri, &wi, position)
1643 .map(|item| vec![item]),
1644 )
1645 })
1646 .await
1647 }
1648
1649 async fn supertypes(
1650 &self,
1651 params: TypeHierarchySupertypesParams,
1652 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1653 guard_async_result("supertypes", async move {
1654 let wi = self.workspace_index_async().await;
1658 let loaded_new = self
1659 .ensure_direct_supertypes_loaded(¶ms.item.name, &wi)
1660 .await;
1661 let wi = if loaded_new {
1662 self.workspace_index_async().await
1663 } else {
1664 wi
1665 };
1666 let result = supertypes_of_from_workspace(¶ms.item, &wi);
1667 Ok(if result.is_empty() {
1668 None
1669 } else {
1670 Some(result)
1671 })
1672 })
1673 .await
1674 }
1675
1676 async fn subtypes(
1677 &self,
1678 params: TypeHierarchySubtypesParams,
1679 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1680 guard_async_result("subtypes", async move {
1681 let wi = self.workspace_index_async().await;
1682 let item_fqn = wi
1688 .classes_by_name
1689 .get(¶ms.item.name)
1690 .and_then(|refs| {
1691 refs.iter()
1692 .filter_map(|r| wi.at(*r))
1693 .find(|(u, _)| **u == params.item.uri)
1694 .or_else(|| refs.first().and_then(|r| wi.at(*r)))
1695 })
1696 .map(|(_, cls)| cls.fqn.as_ref().to_string());
1697 let subtype_urls = item_fqn
1698 .as_deref()
1699 .map(|f| self.docs.class_subtype_urls(f))
1700 .unwrap_or_default();
1701 let result =
1702 subtypes_of_mir_backed(¶ms.item, item_fqn.as_deref(), &wi, &subtype_urls);
1703 Ok(if result.is_empty() {
1704 None
1705 } else {
1706 Some(result)
1707 })
1708 })
1709 .await
1710 }
1711
1712 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
1713 guard_async_result("code_lens", async move {
1714 let uri = ¶ms.text_document.uri;
1715 let doc = match self.get_doc(uri) {
1716 Some(d) => d,
1717 None => return Ok(None),
1718 };
1719 let docs = Arc::clone(&self.docs);
1725 let docs_cancel = Arc::clone(&self.docs);
1726 let uri_owned = uri.clone();
1727 let imports = self.file_imports(uri);
1728 let lenses = self
1729 .blocking("code_lens", move || {
1730 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
1733 code_lenses(
1734 &uri_owned,
1735 &doc,
1736 &docs,
1737 &imports,
1738 Some(cancel_rev),
1739 move || docs_cancel.write_rev() != cancel_rev,
1740 )
1741 })
1742 .await
1743 .unwrap_or_default();
1744 Ok(if lenses.is_empty() {
1745 None
1746 } else {
1747 Some(lenses)
1748 })
1749 })
1750 .await
1751 }
1752
1753 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
1754 let fallback = params.clone();
1755 let resolved = guard_async("code_lens_resolve", async move {
1756 Some(params)
1758 })
1759 .await;
1760 Ok(resolved.unwrap_or(fallback))
1761 }
1762
1763 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
1764 guard_async_result("document_link", async move {
1765 let uri = params.text_document.uri;
1766 let doc = match self.get_doc(&uri) {
1767 Some(d) => d,
1768 None => return Ok(None),
1769 };
1770 let links = self
1773 .blocking_gated(super::debug_gate::GATE_DOCUMENT_LINK, move || {
1774 document_links(&uri, &doc, doc.source())
1775 })
1776 .await
1777 .unwrap_or_default();
1778 Ok(if links.is_empty() { None } else { Some(links) })
1779 })
1780 .await
1781 }
1782
1783 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
1784 let fallback = params.clone();
1785 let resolved = guard_async("document_link_resolve", async move {
1786 Some(params)
1788 })
1789 .await;
1790 Ok(resolved.unwrap_or(fallback))
1791 }
1792
1793 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
1794 guard_async_result("formatting", async move {
1795 let uri = ¶ms.text_document.uri;
1796 let source = self.get_open_text(uri).unwrap_or_default();
1797 Ok(self
1800 .blocking("formatting", move || format_document(&source))
1801 .await
1802 .unwrap_or_default())
1803 })
1804 .await
1805 }
1806
1807 async fn range_formatting(
1808 &self,
1809 params: DocumentRangeFormattingParams,
1810 ) -> Result<Option<Vec<TextEdit>>> {
1811 guard_async_result("range_formatting", async move {
1812 let uri = ¶ms.text_document.uri;
1813 let source = self.get_open_text(uri).unwrap_or_default();
1814 let range = params.range;
1815 Ok(self
1816 .blocking("range_formatting", move || format_range(&source, range))
1817 .await
1818 .unwrap_or_default())
1819 })
1820 .await
1821 }
1822
1823 async fn on_type_formatting(
1824 &self,
1825 params: DocumentOnTypeFormattingParams,
1826 ) -> Result<Option<Vec<TextEdit>>> {
1827 guard_async_result("on_type_formatting", async move {
1828 let uri = ¶ms.text_document_position.text_document.uri;
1829 let source = self.get_open_text(uri).unwrap_or_default();
1830 let position = params.text_document_position.position;
1831 let edits = self
1836 .blocking_gated(super::debug_gate::GATE_ON_TYPE_FORMATTING, move || {
1837 on_type_format(&source, position, ¶ms.ch, ¶ms.options)
1838 })
1839 .await
1840 .unwrap_or_default();
1841 Ok(if edits.is_empty() { None } else { Some(edits) })
1842 })
1843 .await
1844 }
1845
1846 async fn execute_command(
1847 &self,
1848 params: ExecuteCommandParams,
1849 ) -> Result<Option<serde_json::Value>> {
1850 guard_async_result("execute_command", async move {
1851 match params.command.as_str() {
1852 "php-lsp.runTest" => {
1853 let file_uri = params
1855 .arguments
1856 .first()
1857 .and_then(|v| v.as_str())
1858 .and_then(|s| (s).parse::<Uri>().ok());
1859 let filter = params
1860 .arguments
1861 .get(1)
1862 .and_then(|v| v.as_str())
1863 .unwrap_or("")
1864 .to_string();
1865
1866 let root = self.root_paths.load().first().cloned();
1867 let client = self.client.clone();
1868
1869 tokio::spawn(async move {
1870 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
1871 });
1872
1873 Ok(None)
1874 }
1875 _ => Ok(None),
1876 }
1877 })
1878 .await
1879 }
1880
1881 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
1882 guard_async_result("will_rename_files", async move {
1883 self.handle_will_rename_files(params).await
1884 })
1885 .await
1886 }
1887
1888 async fn did_rename_files(&self, params: RenameFilesParams) {
1889 guard_async("did_rename_files", async move {
1890 self.handle_did_rename_files(params).await
1891 })
1892 .await
1893 }
1894
1895 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
1896 guard_async_result("will_create_files", async move {
1897 self.handle_will_create_files(params).await
1898 })
1899 .await
1900 }
1901
1902 async fn did_create_files(&self, params: CreateFilesParams) {
1903 guard_async("did_create_files", async move {
1904 self.handle_did_create_files(params).await
1905 })
1906 .await
1907 }
1908
1909 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
1912 guard_async_result("will_delete_files", async move {
1913 self.handle_will_delete_files(params).await
1914 })
1915 .await
1916 }
1917
1918 async fn did_delete_files(&self, params: DeleteFilesParams) {
1919 guard_async("did_delete_files", async move {
1920 self.handle_did_delete_files(params).await
1921 })
1922 .await
1923 }
1924
1925 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
1926 guard_async_result("moniker", async move {
1927 let uri = ¶ms.text_document_position_params.text_document.uri;
1928 let position = params.text_document_position_params.position;
1929 let source = self.get_open_text(uri).unwrap_or_default();
1930 let doc = match self.get_doc(uri) {
1931 Some(d) => d,
1932 None => return Ok(None),
1933 };
1934 let imports = self.file_imports(uri);
1935 Ok(moniker_at(&source, &doc, position, &imports).map(|m| vec![m]))
1936 })
1937 .await
1938 }
1939
1940 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
1941 guard_async_result("inline_value", async move {
1942 let uri = ¶ms.text_document.uri;
1943 let source = self.get_open_text(uri).unwrap_or_default();
1944 let values = inline_values_in_range(&source, params.range);
1945 Ok(if values.is_empty() {
1946 None
1947 } else {
1948 Some(values)
1949 })
1950 })
1951 .await
1952 }
1953
1954 async fn diagnostic(
1955 &self,
1956 params: DocumentDiagnosticParams,
1957 ) -> Result<DocumentDiagnosticReportResult> {
1958 let report = guard_async("diagnostic", async move {
1959 Some(self.handle_diagnostic(params).await)
1960 })
1961 .await;
1962 match report {
1963 Some(r) => r,
1964 None => Ok(DocumentDiagnosticReportResult::Report(
1965 DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
1966 )),
1967 }
1968 }
1969
1970 async fn workspace_diagnostic(
1971 &self,
1972 params: WorkspaceDiagnosticParams,
1973 ) -> Result<WorkspaceDiagnosticReportResult> {
1974 let report = guard_async("workspace_diagnostic", async move {
1975 Some(self.handle_workspace_diagnostic(params).await)
1976 })
1977 .await;
1978 match report {
1979 Some(r) => r,
1980 None => Ok(WorkspaceDiagnosticReportResult::Report(
1981 WorkspaceDiagnosticReport::default(),
1982 )),
1983 }
1984 }
1985
1986 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
1987 guard_async_result("code_action", async move {
1988 self.handle_code_action(params).await
1989 })
1990 .await
1991 }
1992
1993 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
1994 guard_async_result("code_action_resolve", async move {
1995 self.handle_code_action_resolve(item).await
1996 })
1997 .await
1998 }
1999}
2000
2001fn subtype_site_to_location(file: &str, range: &mir_analyzer::Range) -> Option<Location> {
2004 let uri = (file).parse::<Uri>().ok()?;
2005 let line = range.start.line.saturating_sub(1);
2006 Some(Location {
2007 uri,
2008 range: tower_lsp_server::ls_types::Range {
2009 start: tower_lsp_server::ls_types::Position {
2010 line,
2011 character: range.start.column,
2012 },
2013 end: tower_lsp_server::ls_types::Position {
2014 line,
2015 character: range.end.column,
2016 },
2017 },
2018 })
2019}