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