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