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