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 laravel = self.laravel.load();
922 let laravel_root = self.root_paths.load().first().cloned();
923 let laravel_hover = crate::laravel::hover_for_string_key(
924 &doc,
925 position,
926 &laravel,
927 laravel_root.as_deref(),
928 )
929 .or_else(|| {
930 crate::laravel::blade::hover(
931 uri,
932 &source,
933 position,
934 &laravel,
935 laravel_root.as_deref(),
936 )
937 });
938 drop(laravel);
939 if let Some(hover) = laravel_hover {
940 return Ok(Some(hover));
941 }
942 let open = self.open_urls();
943 let other_docs = self.docs.other_docs(uri, &open);
944 let other_maps = self.docs.other_symbol_maps(uri, &open);
945 let analysis = self.cached_analysis_async(uri).await;
946 let hover_session = self.docs.current_analysis_session();
947 let source_clone = source.clone();
948 let doc_clone = Arc::clone(&doc);
949 let mut wi_cache: Option<Arc<crate::db::workspace_index::WorkspaceIndexData>> = None;
956 let wi = self.workspace_index_cached(&mut wi_cache).await;
957 let docs_for_lookup = Arc::clone(&self.docs);
958 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
959 let cr = wi.resolve_class_ref(name)?;
960 let (uri, _) = wi.at(cr)?;
961 docs_for_lookup.get_doc_salsa(uri)
962 };
963 let result = self
964 .blocking("hover", move || {
965 hover_info_with_maps(
966 &source_clone,
967 &doc_clone,
968 analysis.as_deref(),
969 position,
970 &other_docs,
971 &other_maps,
972 Some(&hover_session),
973 Some(&find_class_doc_fn),
974 )
975 })
976 .await
977 .flatten();
978 if result.is_some() {
979 return Ok(result);
980 }
981 if let Some(word) = crate::text::word_at_position(&source, position) {
986 let wi = self.workspace_index_cached(&mut wi_cache).await;
987 if let Some(h) = class_hover_from_workspace_index(&word, None, &wi) {
989 return Ok(Some(h));
990 }
991 if let Some((resolved, resolved_fqn)) =
995 crate::hover::resolve_use_alias_fqn(&doc.program().stmts, &word)
996 && let Some(h) =
997 class_hover_from_workspace_index(&resolved, Some(&resolved_fqn), &wi)
998 {
999 return Ok(Some(h));
1000 }
1001 if let Some(line_text) = source.lines().nth(position.line as usize)
1003 && let Some(class_token) =
1004 extract_static_class_before_cursor(line_text, position.character as usize)
1005 {
1006 if let Some(h) = method_hover_from_workspace_index(&class_token, &word, &wi) {
1007 return Ok(Some(h));
1008 }
1009 if let Some(resolved_class) =
1010 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
1011 && let Some(h) =
1012 method_hover_from_workspace_index(&resolved_class, &word, &wi)
1013 {
1014 return Ok(Some(h));
1015 }
1016 }
1017 }
1018 Ok(None)
1019 })
1020 .await
1021 }
1022
1023 async fn document_symbol(
1024 &self,
1025 params: DocumentSymbolParams,
1026 ) -> Result<Option<DocumentSymbolResponse>> {
1027 guard_async_result("document_symbol", async move {
1028 let uri = ¶ms.text_document.uri;
1029 let doc = match self.get_doc(uri) {
1030 Some(d) => d,
1031 None => return Ok(None),
1032 };
1033 let symbols = self
1035 .blocking_gated(super::debug_gate::GATE_DOCUMENT_SYMBOL, move || {
1036 document_symbols(doc.source(), &doc)
1037 })
1038 .await
1039 .unwrap_or_default();
1040 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1041 })
1042 .await
1043 }
1044
1045 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
1046 guard_async_result("folding_range", async move {
1047 let uri = ¶ms.text_document.uri;
1048 let doc = match self.get_doc(uri) {
1049 Some(d) => d,
1050 None => return Ok(None),
1051 };
1052 let ranges = self
1055 .blocking_gated(super::debug_gate::GATE_FOLDING_RANGE, move || {
1056 folding_ranges(doc.source(), &doc)
1057 })
1058 .await
1059 .unwrap_or_default();
1060 Ok(if ranges.is_empty() {
1061 None
1062 } else {
1063 Some(ranges)
1064 })
1065 })
1066 .await
1067 }
1068
1069 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
1070 guard_async_result("inlay_hint", async move {
1071 let uri = ¶ms.text_document.uri;
1072 let doc = match self.get_doc(uri) {
1073 Some(d) => d,
1074 None => return Ok(None),
1075 };
1076 let analysis = self.cached_analysis_async(uri).await;
1077 let wi = self.workspace_index_async().await;
1078 let hints = self
1079 .blocking("inlay_hint", move || {
1080 let workspace_defs = wi.func_signatures();
1084 inlay_hints(
1085 doc.source(),
1086 &doc,
1087 analysis.as_deref(),
1088 params.range,
1089 &workspace_defs,
1090 )
1091 })
1092 .await
1093 .unwrap_or_default();
1094 Ok(Some(hints))
1095 })
1096 .await
1097 }
1098
1099 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
1100 let fallback = item.clone();
1101 let resolved = guard_async("inlay_hint_resolve", async move {
1102 if item.tooltip.is_some() {
1103 return Some(item);
1104 }
1105 let func_name = item
1106 .data
1107 .as_ref()
1108 .and_then(|d| d.get("php_lsp_fn"))
1109 .and_then(|v| v.as_str())
1110 .map(str::to_string);
1111 if let Some(name) = func_name {
1112 let docs = Arc::clone(&self.docs);
1113 let tooltip = self
1116 .blocking_gated(super::debug_gate::GATE_INLAY_HINT_RESOLVE, move || {
1117 docs.with_all_indexes(|all_indexes| {
1118 docs_for_symbol_from_index(&name, all_indexes)
1119 })
1120 })
1121 .await
1122 .flatten();
1123 if let Some(md) = tooltip {
1124 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
1125 kind: MarkupKind::Markdown,
1126 value: md,
1127 }));
1128 }
1129 }
1130 Some(item)
1131 })
1132 .await;
1133 Ok(resolved.unwrap_or(fallback))
1134 }
1135
1136 async fn symbol(
1137 &self,
1138 params: WorkspaceSymbolParams,
1139 ) -> Result<Option<WorkspaceSymbolResponse>> {
1140 guard_async_result("symbol", async move {
1141 let wi = self.workspace_index_async().await;
1145 let query = params.query;
1146 let results = self
1147 .blocking("workspace/symbol", move || {
1148 workspace_symbols_from_workspace(&query, &wi)
1149 })
1150 .await
1151 .unwrap_or_default();
1152 Ok(Some(WorkspaceSymbolResponse::Flat(results)))
1153 })
1154 .await
1155 }
1156
1157 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
1158 let docs = self.docs.docs_for(&self.open_urls());
1160 let fallback = params.clone();
1161 let resolved = guard_async("symbol_resolve", async move {
1162 Some(resolve_workspace_symbol(params, &docs))
1163 })
1164 .await;
1165 Ok(resolved.unwrap_or(fallback))
1166 }
1167
1168 #[tracing::instrument(skip_all)]
1169 async fn semantic_tokens_full(
1170 &self,
1171 params: SemanticTokensParams,
1172 ) -> Result<Option<SemanticTokensResult>> {
1173 guard_async_result("semantic_tokens_full", async move {
1174 let uri = params.text_document.uri;
1175 let doc = match self.get_doc(&uri) {
1176 Some(d) => d,
1177 None => {
1178 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1179 result_id: None,
1180 data: vec![],
1181 })));
1182 }
1183 };
1184 let docs = Arc::clone(&self.docs);
1185 let uri_for_task = uri.clone();
1186 let (result_id, data) = self
1189 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_FULL, move || {
1190 let tokens = semantic_tokens(doc.source(), &doc);
1191 let result_id = token_hash(&tokens);
1192 let tokens_arc = Arc::new(tokens);
1193 docs.store_token_cache(
1194 &uri_for_task,
1195 result_id.clone(),
1196 Arc::clone(&tokens_arc),
1197 );
1198 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
1199 (result_id, data)
1200 })
1201 .await
1202 .unwrap_or_default();
1203 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1204 result_id: Some(result_id),
1205 data,
1206 })))
1207 })
1208 .await
1209 }
1210
1211 async fn semantic_tokens_range(
1212 &self,
1213 params: SemanticTokensRangeParams,
1214 ) -> Result<Option<SemanticTokensRangeResult>> {
1215 guard_async_result("semantic_tokens_range", async move {
1216 let uri = params.text_document.uri;
1217 let doc = match self.get_doc(&uri) {
1218 Some(d) => d,
1219 None => {
1220 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1221 result_id: None,
1222 data: vec![],
1223 })));
1224 }
1225 };
1226 let range = params.range;
1227 let tokens = self
1228 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_RANGE, move || {
1229 semantic_tokens_range(doc.source(), &doc, range)
1230 })
1231 .await
1232 .unwrap_or_default();
1233 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1234 result_id: None,
1235 data: tokens,
1236 })))
1237 })
1238 .await
1239 }
1240
1241 async fn semantic_tokens_full_delta(
1242 &self,
1243 params: SemanticTokensDeltaParams,
1244 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
1245 guard_async_result("semantic_tokens_full_delta", async move {
1246 let uri = params.text_document.uri;
1247 let doc = match self.get_doc(&uri) {
1248 Some(d) => d,
1249 None => return Ok(None),
1250 };
1251
1252 let docs = Arc::clone(&self.docs);
1253 let uri_for_task = uri.clone();
1254 let prev_id = params.previous_result_id;
1255 let result = self
1256 .blocking_gated(
1257 super::debug_gate::GATE_SEMANTIC_TOKENS_FULL_DELTA,
1258 move || {
1259 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
1260 let new_result_id = token_hash(&new_tokens);
1261
1262 let result = match docs.get_token_cache(&uri_for_task, &prev_id) {
1263 Some(old_tokens) => {
1264 let edits = compute_token_delta(&old_tokens, &new_tokens);
1265 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
1266 result_id: Some(new_result_id.clone()),
1267 edits,
1268 })
1269 }
1270 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
1272 result_id: Some(new_result_id.clone()),
1273 data: (*new_tokens).clone(),
1274 }),
1275 };
1276
1277 docs.store_token_cache(&uri_for_task, new_result_id, new_tokens);
1278 result
1279 },
1280 )
1281 .await;
1282 let Some(result) = result else {
1283 return Ok(None);
1284 };
1285 Ok(Some(result))
1286 })
1287 .await
1288 }
1289
1290 async fn selection_range(
1291 &self,
1292 params: SelectionRangeParams,
1293 ) -> Result<Option<Vec<SelectionRange>>> {
1294 guard_async_result("selection_range", async move {
1295 let uri = ¶ms.text_document.uri;
1296 let doc = match self.get_doc(uri) {
1297 Some(d) => d,
1298 None => return Ok(None),
1299 };
1300 let ranges = self
1304 .blocking_gated(super::debug_gate::GATE_SELECTION_RANGE, move || {
1305 selection_ranges(&doc, ¶ms.positions)
1306 })
1307 .await
1308 .unwrap_or_default();
1309 Ok(if ranges.is_empty() {
1310 None
1311 } else {
1312 Some(ranges)
1313 })
1314 })
1315 .await
1316 }
1317
1318 async fn prepare_call_hierarchy(
1319 &self,
1320 params: CallHierarchyPrepareParams,
1321 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1322 guard_async_result("prepare_call_hierarchy", async move {
1323 let uri = ¶ms.text_document_position_params.text_document.uri;
1324 let position = params.text_document_position_params.position;
1325 let source = self.get_open_text(uri).unwrap_or_default();
1326 let word = match word_at_position(&source, position) {
1327 Some(w) => w,
1328 None => return Ok(None),
1329 };
1330 if is_bare_keyword_at(&source, position, &word) {
1334 return Ok(None);
1335 }
1336 let wi = self.workspace_index_async().await;
1341 let docs = Arc::clone(&self.docs);
1342 let item = self
1343 .blocking_gated(super::debug_gate::GATE_PREPARE_CALL_HIERARCHY, move || {
1344 let get_doc = move |u: &Uri| docs.get_doc_salsa(u);
1345 prepare_call_hierarchy_indexed(&word, &wi, &get_doc)
1346 })
1347 .await
1348 .flatten();
1349 Ok(item.map(|item| vec![item]))
1350 })
1351 .await
1352 }
1353
1354 async fn incoming_calls(
1355 &self,
1356 params: CallHierarchyIncomingCallsParams,
1357 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1358 guard_async_result("incoming_calls", async move {
1359 let docs = Arc::clone(&self.docs);
1362 let item = params.item;
1363 let calls = self
1364 .blocking("incoming_calls", move || {
1365 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
1368 incoming_calls_indexed(&item, &docs, Some(cancel_rev))
1369 })
1370 .await
1371 .unwrap_or_default();
1372 Ok(if calls.is_empty() { None } else { Some(calls) })
1373 })
1374 .await
1375 }
1376
1377 async fn outgoing_calls(
1378 &self,
1379 params: CallHierarchyOutgoingCallsParams,
1380 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1381 guard_async_result("outgoing_calls", async move {
1382 let wi = self.workspace_index_async().await;
1385 let docs = Arc::clone(&self.docs);
1386 let item = params.item;
1387 let calls = self
1388 .blocking("outgoing_calls", move || {
1389 let get_doc = |u: &Uri| docs.get_doc_salsa(u);
1390 outgoing_calls_indexed(&item, &wi, &get_doc)
1391 })
1392 .await
1393 .unwrap_or_default();
1394 Ok(if calls.is_empty() { None } else { Some(calls) })
1395 })
1396 .await
1397 }
1398
1399 async fn document_highlight(
1400 &self,
1401 params: DocumentHighlightParams,
1402 ) -> Result<Option<Vec<DocumentHighlight>>> {
1403 guard_async_result("document_highlight", async move {
1404 let uri = ¶ms.text_document_position_params.text_document.uri;
1405 let position = params.text_document_position_params.position;
1406 let source = self.get_open_text(uri).unwrap_or_default();
1407 let doc = match self.get_doc_stale(uri) {
1410 Some(d) => d,
1411 None => return Ok(None),
1412 };
1413 let highlights = self
1415 .blocking("document_highlight", move || {
1416 document_highlights(&source, &doc, position)
1417 })
1418 .await
1419 .unwrap_or_default();
1420 Ok(if highlights.is_empty() {
1421 None
1422 } else {
1423 Some(highlights)
1424 })
1425 })
1426 .await
1427 }
1428
1429 async fn linked_editing_range(
1430 &self,
1431 params: LinkedEditingRangeParams,
1432 ) -> Result<Option<LinkedEditingRanges>> {
1433 guard_async_result("linked_editing_range", async move {
1434 self.handle_linked_editing_range(params).await
1435 })
1436 .await
1437 }
1438
1439 async fn goto_implementation(
1440 &self,
1441 params: tower_lsp_server::ls_types::request::GotoImplementationParams,
1442 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoImplementationResponse>> {
1443 guard_async_result("goto_implementation", async move {
1444 let uri = ¶ms.text_document_position_params.text_document.uri;
1445 let position = params.text_document_position_params.position;
1446 let source = self.get_open_text(uri).unwrap_or_default();
1447 let imports = self.file_imports(uri);
1448 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
1449 if raw_word.is_empty() || is_bare_keyword_at(&source, position, &raw_word) {
1450 return Ok(None);
1451 }
1452 let (word, fqn): (String, String) = if raw_word.contains('\\') {
1455 let short = raw_word
1456 .rsplit('\\')
1457 .next()
1458 .unwrap_or(&raw_word)
1459 .to_string();
1460 (short, raw_word.trim_start_matches('\\').to_string())
1461 } else {
1462 let resolved = match self.get_doc(uri) {
1466 Some(doc) => crate::navigation::moniker::resolve_fqn(&doc, &raw_word, &imports),
1467 None => raw_word.clone(),
1468 };
1469 (raw_word, resolved.trim_start_matches('\\').to_string())
1470 };
1471
1472 let doc_for_method_check = self.get_doc(uri);
1478 let on_method_decl = self
1479 .blocking_gated(super::debug_gate::GATE_GOTO_IMPLEMENTATION, move || {
1480 doc_for_method_check.is_some_and(|doc| {
1481 cursor_is_on_method_decl(doc.source(), &doc.program().stmts, position)
1482 })
1483 })
1484 .await
1485 .unwrap_or(false);
1486
1487 let mut locs: Vec<Location> = if on_method_decl {
1490 Vec::new()
1491 } else {
1492 let docs = Arc::clone(&self.docs);
1493 let fqn_task = fqn.clone();
1494 self.blocking("goto_implementation.subtypes", move || {
1495 docs.indexed_subtype_classes(&fqn_task, false)
1496 })
1497 .await
1498 .unwrap_or_default()
1499 .into_iter()
1500 .filter_map(|site| subtype_site_to_location(&site.file, &site.range))
1501 .collect()
1502 };
1503
1504 if locs.is_empty()
1507 && let Some(doc) = self.get_doc(uri)
1508 && let Some(enclosing) =
1509 crate::types::type_map::enclosing_class_at(&source, &doc, position)
1510 {
1511 let enclosing_fqn =
1512 crate::navigation::moniker::resolve_fqn(&doc, &enclosing, &imports)
1513 .trim_start_matches('\\')
1514 .to_string();
1515 let docs = Arc::clone(&self.docs);
1516 let method = word.clone();
1517 let impls = self
1518 .blocking("goto_implementation.method_impls", move || {
1519 docs.indexed_method_implementations(&enclosing_fqn, &method)
1520 })
1521 .await
1522 .unwrap_or_default();
1523 locs = impls
1524 .into_iter()
1525 .filter_map(|(_, file, range)| subtype_site_to_location(&file, &range))
1526 .collect();
1527 }
1528 if locs.is_empty() {
1529 Ok(None)
1530 } else {
1531 Ok(Some(GotoDefinitionResponse::Array(locs)))
1532 }
1533 })
1534 .await
1535 }
1536
1537 async fn goto_declaration(
1538 &self,
1539 params: tower_lsp_server::ls_types::request::GotoDeclarationParams,
1540 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoDeclarationResponse>> {
1541 guard_async_result("goto_declaration", async move {
1542 let uri = ¶ms.text_document_position_params.text_document.uri;
1543 let position = params.text_document_position_params.position;
1544 let source = self.get_open_text(uri).unwrap_or_default();
1545 let open_docs = self.docs.docs_for(&self.open_urls());
1551 let source_for_pass1 = source.clone();
1552 let first_pass = self
1553 .blocking("goto_declaration.open_docs", move || {
1554 goto_declaration(&source_for_pass1, &open_docs, position)
1555 })
1556 .await
1557 .unwrap_or_default();
1558 if let Some(loc) = first_pass {
1559 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1560 }
1561 let wi = self.workspace_index_async().await;
1564 let second_pass = self
1565 .blocking("goto_declaration.index", move || {
1566 goto_declaration_from_index(&source, &wi, position)
1567 })
1568 .await
1569 .unwrap_or_default();
1570 Ok(second_pass.map(GotoDefinitionResponse::Scalar))
1571 })
1572 .await
1573 }
1574
1575 async fn goto_type_definition(
1576 &self,
1577 params: tower_lsp_server::ls_types::request::GotoTypeDefinitionParams,
1578 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoTypeDefinitionResponse>> {
1579 guard_async_result("goto_type_definition", async move {
1580 let uri = ¶ms.text_document_position_params.text_document.uri;
1581 let position = params.text_document_position_params.position;
1582 let source = self.get_open_text(uri).unwrap_or_default();
1583 let doc = match self.get_doc(uri) {
1584 Some(d) => d,
1585 None => return Ok(None),
1586 };
1587 let analysis = self.cached_analysis_async(uri).await;
1588 let open_docs = self.docs.docs_for(&self.open_urls());
1589 let docs = Arc::clone(&self.docs);
1590
1591 let response = self
1596 .blocking_gated(super::debug_gate::GATE_GOTO_TYPE_DEFINITION, move || {
1597 let mut results = goto_type_definition_exact(
1602 &source,
1603 &doc,
1604 analysis.as_deref(),
1605 &open_docs,
1606 position,
1607 );
1608 if results.is_empty() {
1609 results = docs.with_all_indexes(|all_indexes| {
1610 goto_type_definition_from_index_exact(
1611 &source,
1612 &doc,
1613 analysis.as_deref(),
1614 all_indexes,
1615 position,
1616 )
1617 });
1618 }
1619 if results.is_empty() {
1620 results = goto_type_definition_short_name_fallback(
1621 &source,
1622 &doc,
1623 analysis.as_deref(),
1624 &open_docs,
1625 position,
1626 );
1627 }
1628 if results.is_empty() {
1629 results = docs.with_all_indexes(|all_indexes| {
1630 goto_type_definition_from_index_short_name_fallback(
1631 &source,
1632 &doc,
1633 analysis.as_deref(),
1634 all_indexes,
1635 position,
1636 )
1637 });
1638 }
1639
1640 match results.len() {
1642 0 => None,
1643 1 => Some(GotoDefinitionResponse::Scalar(
1644 results.into_iter().next().unwrap(),
1645 )),
1646 _ => Some(GotoDefinitionResponse::Array(results)),
1647 }
1648 })
1649 .await
1650 .unwrap_or_default();
1651 Ok(response)
1652 })
1653 .await
1654 }
1655
1656 async fn prepare_type_hierarchy(
1657 &self,
1658 params: TypeHierarchyPrepareParams,
1659 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1660 guard_async_result("prepare_type_hierarchy", async move {
1661 let uri = ¶ms.text_document_position_params.text_document.uri;
1662 let position = params.text_document_position_params.position;
1663 let source = self.get_open_text(uri).unwrap_or_default();
1664 let wi = self.workspace_index_async().await;
1666 Ok(
1667 prepare_type_hierarchy_from_workspace(&source, uri, &wi, position)
1668 .map(|item| vec![item]),
1669 )
1670 })
1671 .await
1672 }
1673
1674 async fn supertypes(
1675 &self,
1676 params: TypeHierarchySupertypesParams,
1677 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1678 guard_async_result("supertypes", async move {
1679 let wi = self.workspace_index_async().await;
1683 let loaded_new = self
1684 .ensure_direct_supertypes_loaded(¶ms.item.name, &wi)
1685 .await;
1686 let wi = if loaded_new {
1687 self.workspace_index_async().await
1688 } else {
1689 wi
1690 };
1691 let result = supertypes_of_from_workspace(¶ms.item, &wi);
1692 Ok(if result.is_empty() {
1693 None
1694 } else {
1695 Some(result)
1696 })
1697 })
1698 .await
1699 }
1700
1701 async fn subtypes(
1702 &self,
1703 params: TypeHierarchySubtypesParams,
1704 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1705 guard_async_result("subtypes", async move {
1706 let wi = self.workspace_index_async().await;
1707 let item_fqn = wi
1713 .classes_by_name
1714 .get(¶ms.item.name)
1715 .and_then(|refs| {
1716 refs.iter()
1717 .filter_map(|r| wi.at(*r))
1718 .find(|(u, _)| **u == params.item.uri)
1719 .or_else(|| refs.first().and_then(|r| wi.at(*r)))
1720 })
1721 .map(|(_, cls)| cls.fqn.as_ref().to_string());
1722 let subtype_urls = item_fqn
1723 .as_deref()
1724 .map(|f| self.docs.class_subtype_urls(f))
1725 .unwrap_or_default();
1726 let result =
1727 subtypes_of_mir_backed(¶ms.item, item_fqn.as_deref(), &wi, &subtype_urls);
1728 Ok(if result.is_empty() {
1729 None
1730 } else {
1731 Some(result)
1732 })
1733 })
1734 .await
1735 }
1736
1737 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
1738 guard_async_result("code_lens", async move {
1739 let uri = ¶ms.text_document.uri;
1740 let doc = match self.get_doc(uri) {
1741 Some(d) => d,
1742 None => return Ok(None),
1743 };
1744 let docs = Arc::clone(&self.docs);
1750 let docs_cancel = Arc::clone(&self.docs);
1751 let uri_owned = uri.clone();
1752 let imports = self.file_imports(uri);
1753 let lenses = self
1754 .blocking("code_lens", move || {
1755 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
1758 code_lenses(
1759 &uri_owned,
1760 &doc,
1761 &docs,
1762 &imports,
1763 Some(cancel_rev),
1764 move || docs_cancel.write_rev() != cancel_rev,
1765 )
1766 })
1767 .await
1768 .unwrap_or_default();
1769 Ok(if lenses.is_empty() {
1770 None
1771 } else {
1772 Some(lenses)
1773 })
1774 })
1775 .await
1776 }
1777
1778 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
1779 let fallback = params.clone();
1780 let resolved = guard_async("code_lens_resolve", async move {
1781 Some(params)
1783 })
1784 .await;
1785 Ok(resolved.unwrap_or(fallback))
1786 }
1787
1788 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
1789 guard_async_result("document_link", async move {
1790 let uri = params.text_document.uri;
1791 let doc = match self.get_doc(&uri) {
1792 Some(d) => d,
1793 None => return Ok(None),
1794 };
1795 let laravel = self.laravel.load_full();
1796 let links = self
1799 .blocking_gated(super::debug_gate::GATE_DOCUMENT_LINK, move || {
1800 let mut links = document_links(&uri, &doc, doc.source());
1801 links.extend(crate::laravel::document_links(&doc, &laravel));
1802 links.extend(crate::laravel::blade::document_links(
1803 &uri,
1804 doc.source(),
1805 &laravel,
1806 ));
1807 links
1808 })
1809 .await
1810 .unwrap_or_default();
1811 Ok(if links.is_empty() { None } else { Some(links) })
1812 })
1813 .await
1814 }
1815
1816 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
1817 let fallback = params.clone();
1818 let resolved = guard_async("document_link_resolve", async move {
1819 Some(params)
1821 })
1822 .await;
1823 Ok(resolved.unwrap_or(fallback))
1824 }
1825
1826 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
1827 guard_async_result("formatting", async move {
1828 let uri = ¶ms.text_document.uri;
1829 let source = self.get_open_text(uri).unwrap_or_default();
1830 Ok(self
1833 .blocking("formatting", move || format_document(&source))
1834 .await
1835 .unwrap_or_default())
1836 })
1837 .await
1838 }
1839
1840 async fn range_formatting(
1841 &self,
1842 params: DocumentRangeFormattingParams,
1843 ) -> Result<Option<Vec<TextEdit>>> {
1844 guard_async_result("range_formatting", async move {
1845 let uri = ¶ms.text_document.uri;
1846 let source = self.get_open_text(uri).unwrap_or_default();
1847 let range = params.range;
1848 Ok(self
1849 .blocking("range_formatting", move || format_range(&source, range))
1850 .await
1851 .unwrap_or_default())
1852 })
1853 .await
1854 }
1855
1856 async fn on_type_formatting(
1857 &self,
1858 params: DocumentOnTypeFormattingParams,
1859 ) -> Result<Option<Vec<TextEdit>>> {
1860 guard_async_result("on_type_formatting", async move {
1861 let uri = ¶ms.text_document_position.text_document.uri;
1862 let source = self.get_open_text(uri).unwrap_or_default();
1863 let position = params.text_document_position.position;
1864 let edits = self
1869 .blocking_gated(super::debug_gate::GATE_ON_TYPE_FORMATTING, move || {
1870 on_type_format(&source, position, ¶ms.ch, ¶ms.options)
1871 })
1872 .await
1873 .unwrap_or_default();
1874 Ok(if edits.is_empty() { None } else { Some(edits) })
1875 })
1876 .await
1877 }
1878
1879 async fn execute_command(
1880 &self,
1881 params: ExecuteCommandParams,
1882 ) -> Result<Option<serde_json::Value>> {
1883 guard_async_result("execute_command", async move {
1884 match params.command.as_str() {
1885 "php-lsp.runTest" => {
1886 let file_uri = params
1888 .arguments
1889 .first()
1890 .and_then(|v| v.as_str())
1891 .and_then(|s| (s).parse::<Uri>().ok());
1892 let filter = params
1893 .arguments
1894 .get(1)
1895 .and_then(|v| v.as_str())
1896 .unwrap_or("")
1897 .to_string();
1898
1899 let root = self.root_paths.load().first().cloned();
1900 let client = self.client.clone();
1901
1902 tokio::spawn(async move {
1903 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
1904 });
1905
1906 Ok(None)
1907 }
1908 _ => Ok(None),
1909 }
1910 })
1911 .await
1912 }
1913
1914 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
1915 guard_async_result("will_rename_files", async move {
1916 self.handle_will_rename_files(params).await
1917 })
1918 .await
1919 }
1920
1921 async fn did_rename_files(&self, params: RenameFilesParams) {
1922 guard_async("did_rename_files", async move {
1923 self.handle_did_rename_files(params).await
1924 })
1925 .await
1926 }
1927
1928 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
1929 guard_async_result("will_create_files", async move {
1930 self.handle_will_create_files(params).await
1931 })
1932 .await
1933 }
1934
1935 async fn did_create_files(&self, params: CreateFilesParams) {
1936 guard_async("did_create_files", async move {
1937 self.handle_did_create_files(params).await
1938 })
1939 .await
1940 }
1941
1942 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
1945 guard_async_result("will_delete_files", async move {
1946 self.handle_will_delete_files(params).await
1947 })
1948 .await
1949 }
1950
1951 async fn did_delete_files(&self, params: DeleteFilesParams) {
1952 guard_async("did_delete_files", async move {
1953 self.handle_did_delete_files(params).await
1954 })
1955 .await
1956 }
1957
1958 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
1959 guard_async_result("moniker", async move {
1960 let uri = ¶ms.text_document_position_params.text_document.uri;
1961 let position = params.text_document_position_params.position;
1962 let source = self.get_open_text(uri).unwrap_or_default();
1963 let doc = match self.get_doc(uri) {
1964 Some(d) => d,
1965 None => return Ok(None),
1966 };
1967 let imports = self.file_imports(uri);
1968 Ok(moniker_at(&source, &doc, position, &imports).map(|m| vec![m]))
1969 })
1970 .await
1971 }
1972
1973 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
1974 guard_async_result("inline_value", async move {
1975 let uri = ¶ms.text_document.uri;
1976 let source = self.get_open_text(uri).unwrap_or_default();
1977 let values = inline_values_in_range(&source, params.range);
1978 Ok(if values.is_empty() {
1979 None
1980 } else {
1981 Some(values)
1982 })
1983 })
1984 .await
1985 }
1986
1987 async fn diagnostic(
1988 &self,
1989 params: DocumentDiagnosticParams,
1990 ) -> Result<DocumentDiagnosticReportResult> {
1991 let report = guard_async("diagnostic", async move {
1992 Some(self.handle_diagnostic(params).await)
1993 })
1994 .await;
1995 match report {
1996 Some(r) => r,
1997 None => Ok(DocumentDiagnosticReportResult::Report(
1998 DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
1999 )),
2000 }
2001 }
2002
2003 async fn workspace_diagnostic(
2004 &self,
2005 params: WorkspaceDiagnosticParams,
2006 ) -> Result<WorkspaceDiagnosticReportResult> {
2007 let report = guard_async("workspace_diagnostic", async move {
2008 Some(self.handle_workspace_diagnostic(params).await)
2009 })
2010 .await;
2011 match report {
2012 Some(r) => r,
2013 None => Ok(WorkspaceDiagnosticReportResult::Report(
2014 WorkspaceDiagnosticReport::default(),
2015 )),
2016 }
2017 }
2018
2019 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
2020 guard_async_result("code_action", async move {
2021 self.handle_code_action(params).await
2022 })
2023 .await
2024 }
2025
2026 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
2027 guard_async_result("code_action_resolve", async move {
2028 self.handle_code_action_resolve(item).await
2029 })
2030 .await
2031 }
2032}
2033
2034fn subtype_site_to_location(file: &str, range: &mir_analyzer::Range) -> Option<Location> {
2037 let uri = (file).parse::<Uri>().ok()?;
2038 let line = range.start.line.saturating_sub(1);
2039 Some(Location {
2040 uri,
2041 range: tower_lsp_server::ls_types::Range {
2042 start: tower_lsp_server::ls_types::Position {
2043 line,
2044 character: range.start.column,
2045 },
2046 end: tower_lsp_server::ls_types::Position {
2047 line,
2048 character: range.end.column,
2049 },
2050 },
2051 })
2052}