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, extract_static_class_before_cursor,
13 hover_info_with_maps, method_hover_from_index, signature_for_symbol_from_index,
14};
15use crate::index::workspace_scan::{scan_workspace, send_refresh_requests};
16use crate::lang::config::LspConfig;
17use crate::navigation::symbols::{
18 document_symbols, resolve_workspace_symbol, workspace_symbols_from_workspace,
19};
20use crate::text::word_at_position;
21
22use crate::navigation::call_hierarchy::{
23 incoming_calls, outgoing_calls_indexed, prepare_call_hierarchy_indexed,
24};
25use crate::navigation::declaration::{goto_declaration, goto_declaration_from_index};
26use crate::navigation::implementation::{
27 find_implementations, find_implementations_from_workspace,
28 find_method_implementations_from_workspace,
29};
30use crate::navigation::moniker::moniker_at;
31use crate::navigation::type_definition::{goto_type_definition, goto_type_definition_from_index};
32use crate::navigation::type_hierarchy::{
33 prepare_type_hierarchy_from_workspace, subtypes_of_from_workspace, supertypes_of_from_workspace,
34};
35
36use crate::analysis::code_lens::code_lenses;
37use crate::analysis::diagnostics::{diagnostics_from_doc, parse_document, parse_document_no_diags};
38use crate::analysis::document_highlight::document_highlights;
39use crate::analysis::inlay_hints::inlay_hints;
40use crate::analysis::inline_value::inline_values_in_range;
41use crate::analysis::semantic_tokens::{
42 compute_token_delta, semantic_tokens, semantic_tokens_range, token_hash,
43};
44
45use crate::editing::document_link::document_links;
46use crate::editing::folding::folding_ranges;
47use crate::editing::formatting::{format_document, format_range};
48use crate::editing::on_type_format::on_type_format;
49use crate::editing::rename::{prepare_rename, rename, rename_property, rename_variable};
50use crate::editing::selection_range::selection_ranges;
51use crate::editing::signature_help::signature_help;
52
53use super::helpers::{
54 cursor_is_on_property_decl, is_after_arrow, promoted_property_at_cursor, run_phpunit,
55};
56use super::{Backend, publish_with_dependents};
57
58#[async_trait]
59impl LanguageServer for Backend {
60 async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
61 self.handle_initialize(params).await
62 }
63
64 async fn initialized(&self, _params: InitializedParams) {
65 guard_async("initialized", async move {
66 self.handle_initialized(_params).await
67 })
68 .await
69 }
70
71 async fn did_change_configuration(&self, _params: DidChangeConfigurationParams) {
72 guard_async("did_change_configuration", async move {
73 let items = vec![ConfigurationItem {
76 scope_uri: None,
77 section: Some("php-lsp".to_string()),
78 }];
79 if let Ok(values) = self.client.configuration(items).await
80 && let Some(value) = values.into_iter().next()
81 {
82 let roots = self.root_paths.load_full();
83
84 let file_cfg = crate::lang::autoload::load_project_config_json(&roots);
87
88 if let Some(ver) = value.get("phpVersion").and_then(|v| v.as_str())
89 && !crate::lang::autoload::is_valid_php_version(ver)
90 {
91 self.client
92 .log_message(
93 tower_lsp::lsp_types::MessageType::WARNING,
94 format!(
95 "php-lsp: unsupported phpVersion {ver:?} — valid values: {}",
96 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
97 ),
98 )
99 .await;
100 }
101
102 let file_obj = file_cfg.as_ref().filter(|v| v.is_object());
103 let merged = LspConfig::merge_project_configs(file_obj, Some(&value));
104 let mut cfg = LspConfig::from_value(&merged);
105
106 let (ver, source) = self.resolve_php_version(cfg.php_version.as_deref());
108 self.client
109 .log_message(
110 tower_lsp::lsp_types::MessageType::INFO,
111 format!("php-lsp: using PHP {ver} ({source})"),
112 )
113 .await;
114 let ver = if source != "set by editor"
116 && !crate::lang::autoload::is_valid_php_version(&ver)
117 {
118 let clamped = crate::lang::autoload::clamp_php_version(&ver);
119 self.client
120 .show_message(
121 tower_lsp::lsp_types::MessageType::WARNING,
122 format!(
123 "php-lsp: detected PHP {ver} is outside the supported range ({}); \
124 using PHP {clamped} for analysis",
125 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
126 ),
127 )
128 .await;
129 clamped.to_string()
130 } else {
131 ver
132 };
133 cfg.php_version = Some(ver.clone());
134 if let Ok(pv) = ver.parse::<mir_analyzer::PhpVersion>() {
135 self.docs.set_php_version(pv);
136 }
137 self.config.store(Arc::new(cfg));
138 send_refresh_requests(&self.client).await;
139 }
140 })
141 .await
142 }
143
144 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
145 guard_async("did_change_workspace_folders", async move {
146 {
148 let mut roots = (**self.root_paths.load()).clone();
149 for removed in ¶ms.event.removed {
150 if let Ok(path) = removed.uri.to_file_path() {
151 roots.retain(|r| r != &path);
152 }
153 }
154 self.root_paths.store(Arc::new(roots));
155 }
156
157 let (exclude_paths, include_paths, max_indexed_files) = {
159 let cfg = self.config.load();
160 (
161 cfg.exclude_paths.clone(),
162 cfg.include_paths.clone(),
163 cfg.max_indexed_files,
164 )
165 };
166 for added in ¶ms.event.added {
167 if let Ok(path) = added.uri.to_file_path() {
168 let is_new = {
169 let mut roots = (**self.root_paths.load()).clone();
170 if !roots.contains(&path) {
171 roots.push(path.clone());
172 self.root_paths.store(Arc::new(roots));
173 true
174 } else {
175 false
176 }
177 };
178 if is_new {
179 let docs = Arc::clone(&self.docs);
180 let open_files = self.open_files.clone();
181 let ex = exclude_paths.clone();
182 let ip = include_paths.clone();
183 let path_clone = path.clone();
184 let client = self.client.clone();
185 tokio::spawn(async move {
186 let cache = crate::index::cache::WorkspaceCache::new(&path_clone);
187 scan_workspace(
188 path_clone,
189 docs,
190 open_files,
191 cache,
192 &ex,
193 &ip,
194 max_indexed_files,
195 )
196 .await;
197 send_refresh_requests(&client).await;
198 });
199 }
200 }
201 }
202 })
203 .await
204 }
205
206 async fn shutdown(&self) -> Result<()> {
207 Ok(())
208 }
209
210 #[tracing::instrument(skip_all)]
211 async fn did_open(&self, params: DidOpenTextDocumentParams) {
212 guard_async("did_open", async move {
213 let uri = params.text_document.uri;
214 let text = params.text_document.text;
215
216 self.set_open_text(uri.clone(), text);
220
221 let parse_diags = self
224 .docs
225 .get_doc_salsa(&uri)
226 .map(|doc| diagnostics_from_doc(&doc))
227 .unwrap_or_default();
228 self.set_parse_diagnostics(&uri, parse_diags);
229
230 publish_with_dependents(
231 self.client.clone(),
232 Arc::clone(&self.docs),
233 self.open_files.clone(),
234 uri,
235 self.config.load().diagnostics.clone(),
236 )
237 .await;
238 })
239 .await
240 }
241
242 #[tracing::instrument(skip_all)]
243 async fn did_change(&self, params: DidChangeTextDocumentParams) {
244 guard_async("did_change", async move {
245 let uri = params.text_document.uri;
246 let mut updated: Option<String> = None;
251 for change in params.content_changes {
252 match change.range {
253 None => updated = Some(change.text),
254 Some(range) => {
255 let mut cur = match updated.take() {
256 Some(t) => t,
257 None => self.get_open_text(&uri).unwrap_or_default(),
258 };
259 crate::text::apply_content_change(&mut cur, range, &change.text);
260 updated = Some(cur);
261 }
262 }
263 }
264 let Some(text) = updated else { return };
265
266 let version = self.set_open_text(uri.clone(), text.clone());
270
271 let docs = Arc::clone(&self.docs);
272 let open_files = self.open_files.clone();
273 let client = self.client.clone();
274 let cfg = self.config.load();
275 let diag_cfg = cfg.diagnostics.clone();
276 let debounce_ms = cfg.debounce_ms;
277 tokio::spawn(async move {
278 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
281
282 if open_files.current_version(&uri) != Some(version) {
286 return;
287 }
288
289 let (_doc, parse_diags) =
290 tokio::task::spawn_blocking(move || parse_document(&text))
291 .await
292 .unwrap_or_else(|_| (ParsedDoc::default(), vec![]));
293
294 if open_files.current_version(&uri) == Some(version) {
296 open_files.set_parse_diagnostics(&uri, parse_diags);
297 publish_with_dependents(client, docs, open_files, uri, diag_cfg).await;
298 }
299 });
300 })
301 .await
302 }
303
304 async fn did_close(&self, params: DidCloseTextDocumentParams) {
305 guard_async("did_close", async move {
306 let uri = params.text_document.uri;
307 self.close_open_file(&uri);
308 let disk_text = match uri.to_file_path() {
313 Ok(path) => tokio::fs::read_to_string(&path).await.ok(),
314 Err(_) => None,
315 };
316 if !self.open_files.contains(&uri) {
320 match disk_text {
321 Some(text) => self.docs.ingest(uri.clone(), &text),
322 None => self.docs.remove(&uri),
323 }
324 }
325 self.client.publish_diagnostics(uri, vec![], None).await;
327 })
328 .await
329 }
330
331 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
332
333 async fn will_save_wait_until(
334 &self,
335 params: WillSaveTextDocumentParams,
336 ) -> Result<Option<Vec<TextEdit>>> {
337 guard_async_result("will_save_wait_until", async move {
338 let source = self
339 .get_open_text(¶ms.text_document.uri)
340 .unwrap_or_default();
341 Ok(format_document(&source))
342 })
343 .await
344 }
345
346 async fn did_save(&self, params: DidSaveTextDocumentParams) {
347 guard_async("did_save", async move {
348 let uri = params.text_document.uri;
349 let diag_cfg = self.config.load().diagnostics.clone();
355 let all = compute_open_file_diagnostics(&self.docs, &self.open_files, &uri, &diag_cfg);
356 self.client
357 .publish_diagnostics(uri.clone(), all, None)
358 .await;
359
360 if let (Some(root), Ok(path)) =
372 (self.root_paths.load().first().cloned(), uri.to_file_path())
373 {
374 tokio::task::spawn_blocking(move || {
375 let Some(cache) = crate::index::cache::WorkspaceCache::new(&root) else {
376 return;
377 };
378 let Ok(meta) = std::fs::metadata(&path) else {
381 return;
382 };
383 let Ok(text) = std::fs::read_to_string(&path) else {
384 return;
385 };
386 let mtime_secs = meta
387 .modified()
388 .ok()
389 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
390 .map(|d| d.as_secs())
391 .unwrap_or(0);
392 let key = crate::index::cache::WorkspaceCache::key_for_stat(
393 uri.as_str(),
394 mtime_secs,
395 meta.len(),
396 );
397 let doc = parse_document_no_diags(&text);
398 let index = crate::index::file_index::FileIndex::extract(&doc);
399 let _ = cache.write(&key, &index);
400 });
401 }
402 })
403 .await
404 }
405
406 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
407 guard_async("did_change_watched_files", async move {
408 for change in params.changes {
409 match change.typ {
410 FileChangeType::CREATED | FileChangeType::CHANGED => {
411 if let Ok(path) = change.uri.to_file_path()
412 && let Ok(text) = tokio::fs::read_to_string(&path).await
413 {
414 let doc = parse_document_no_diags(&text);
419 self.ingest_from_doc_if_not_open(change.uri.clone(), &doc);
420 }
421 }
422 FileChangeType::DELETED => {
423 self.docs.remove(&change.uri);
424 }
425 _ => {}
426 }
427 }
428 send_refresh_requests(&self.client).await;
430 })
431 .await
432 }
433
434 #[tracing::instrument(skip_all)]
435 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
436 guard_async_result("completion", async move {
437 let uri = ¶ms.text_document_position.text_document.uri;
438 let position = params.text_document_position.position;
439 let source = self.get_open_text(uri).unwrap_or_default();
440 let doc = match self.get_doc(uri) {
441 Some(d) => d,
442 None => return Ok(Some(CompletionResponse::Array(vec![]))),
443 };
444 let other_docs: Vec<Arc<ParsedDoc>> = self
445 .docs
446 .other_docs(uri, &self.open_urls())
447 .into_iter()
448 .map(|(_, d)| d)
449 .collect();
450 let trigger = params
451 .context
452 .as_ref()
453 .and_then(|c| c.trigger_character.as_deref());
454 let meta_loaded = self.meta.load();
455 let meta_opt = if meta_loaded.is_empty() {
456 None
457 } else {
458 Some(&**meta_loaded)
459 };
460 let imports = self.file_imports(uri);
461 let wi = self.workspace_index_async().await;
462 let docs_for_lookup = Arc::clone(&self.docs);
463 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
464 let cr = *wi.classes_by_name.get(name)?.first()?;
465 let (uri, _) = wi.at(cr)?;
466 docs_for_lookup.get_doc_salsa(uri)
467 };
468 let analysis = self.cached_analysis_async(uri).await;
469 let docs_for_tm = Arc::clone(&self.docs);
473 let doc_for_tm = Arc::clone(&doc);
474 let uri_for_tm = uri.clone();
475 let get_type_map =
476 move || docs_for_tm.cached_type_map(&uri_for_tm, &doc_for_tm, meta_opt);
477 let session = self
478 .docs
479 .analysis_session(self.docs.workspace_php_version());
480 let ctx = CompletionCtx {
481 source: Some(&source),
482 position: Some(position),
483 meta: meta_opt,
484 doc_uri: Some(uri),
485 file_imports: Some(&imports),
486 find_class_doc: Some(&find_class_doc_fn),
487 analysis: analysis.as_deref(),
488 type_map: Some(&get_type_map),
489 session: Some(session),
490 };
491 Ok(Some(CompletionResponse::Array(filtered_completions_at(
492 &doc,
493 &other_docs,
494 trigger,
495 &ctx,
496 ))))
497 })
498 .await
499 }
500
501 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
502 guard_async_result("completion_resolve", async move {
503 if item.documentation.is_some() && item.detail.is_some() {
504 return Ok(item);
505 }
506 let name = item.label.trim_end_matches(':');
508 let all_indexes = self.docs.all_indexes();
509 if item.detail.is_none()
510 && let Some(sig) = signature_for_symbol_from_index(name, &all_indexes)
511 {
512 item.detail = Some(sig);
513 }
514 if item.documentation.is_none()
515 && let Some(md) = docs_for_symbol_from_index(name, &all_indexes)
516 {
517 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
518 kind: MarkupKind::Markdown,
519 value: md,
520 }));
521 }
522 Ok(item)
523 })
524 .await
525 }
526
527 async fn goto_definition(
528 &self,
529 params: GotoDefinitionParams,
530 ) -> Result<Option<GotoDefinitionResponse>> {
531 guard_async_result("goto_definition", async move {
532 self.handle_goto_definition(params).await
533 })
534 .await
535 }
536
537 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
538 guard_async_result(
539 "references",
540 async move { self.handle_references(params).await },
541 )
542 .await
543 }
544
545 async fn prepare_rename(
546 &self,
547 params: TextDocumentPositionParams,
548 ) -> Result<Option<PrepareRenameResponse>> {
549 guard_async_result("prepare_rename", async move {
550 let uri = ¶ms.text_document.uri;
551 let source = self.get_open_text(uri).unwrap_or_default();
552 Ok(prepare_rename(&source, params.position).map(PrepareRenameResponse::Range))
553 })
554 .await
555 }
556
557 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
558 guard_async_result("rename", async move {
559 let uri = ¶ms.text_document_position.text_document.uri;
560 let position = params.text_document_position.position;
561 let source = self.get_open_text(uri).unwrap_or_default();
562 let word = match word_at_position(&source, position) {
563 Some(w) => w,
564 None => return Ok(None),
565 };
566 if word.starts_with('$') {
567 let doc = match self.get_doc(uri) {
568 Some(d) => d,
569 None => return Ok(None),
570 };
571 let prop_name = cursor_is_on_property_decl(&source, &doc.program().stmts, position)
575 .or_else(|| {
576 promoted_property_at_cursor(&source, &doc.program().stmts, position)
577 });
578 if let Some(prop_name) = prop_name {
579 let all_docs = self.docs.all_docs_for_scan();
580 return Ok(Some(rename_property(
581 &prop_name,
582 ¶ms.new_name,
583 &all_docs,
584 )));
585 }
586 Ok(Some(rename_variable(
587 &word,
588 ¶ms.new_name,
589 uri,
590 &doc,
591 position,
592 )))
593 } else if is_after_arrow(&source, position) {
594 let all_docs = self.docs.all_docs_for_scan();
595 Ok(Some(rename_property(&word, ¶ms.new_name, &all_docs)))
596 } else {
597 let all_docs = self.docs.all_docs_for_scan();
598 let doc_opt = self.get_doc(uri);
599 let target_fqn: Option<String> = doc_opt.as_ref().map(|doc| {
600 let imports = self.file_imports(uri);
601 crate::navigation::moniker::resolve_fqn(doc, &word, &imports)
602 });
603 Ok(Some(rename(
604 &word,
605 ¶ms.new_name,
606 &all_docs,
607 target_fqn.as_deref(),
608 )))
609 }
610 })
611 .await
612 }
613
614 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
615 guard_async_result("signature_help", async move {
616 let uri = ¶ms.text_document_position_params.text_document.uri;
617 let position = params.text_document_position_params.position;
618 let source = self.get_open_text(uri).unwrap_or_default();
619 let doc = match self.get_doc(uri) {
620 Some(d) => d,
621 None => return Ok(None),
622 };
623 let all_indexes = self.docs.all_indexes();
624 Ok(signature_help(&source, &doc, position, &all_indexes))
625 })
626 .await
627 }
628
629 #[tracing::instrument(skip_all)]
630 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
631 guard_async_result("hover", async move {
632 let uri = ¶ms.text_document_position_params.text_document.uri;
633 let position = params.text_document_position_params.position;
634 let source = self.get_open_text(uri).unwrap_or_default();
635 let doc = match self.get_doc(uri) {
636 Some(d) => d,
637 None => return Ok(None),
638 };
639 let other_docs = self.docs.other_docs(uri, &self.open_urls());
640 let other_maps = self.docs.other_symbol_maps(uri, &self.open_urls());
641 let analysis = self.cached_analysis_async(uri).await;
642 let hover_session = self
643 .docs
644 .analysis_session(self.docs.workspace_php_version());
645 let result = hover_info_with_maps(
646 &source,
647 &doc,
648 analysis.as_deref(),
649 position,
650 &other_docs,
651 &other_maps,
652 Some(&hover_session),
653 );
654 if result.is_some() {
655 return Ok(result);
656 }
657 if let Some(word) = crate::text::word_at_position(&source, position) {
662 let wi = self.workspace_index_async().await;
663 if let Some(h) = class_hover_from_index(&word, &wi.files) {
665 return Ok(Some(h));
666 }
667 if let Some(resolved) = crate::hover::resolve_use_alias(&doc.program().stmts, &word)
669 && let Some(h) = class_hover_from_index(&resolved, &wi.files)
670 {
671 return Ok(Some(h));
672 }
673 if let Some(line_text) = source.lines().nth(position.line as usize)
675 && let Some(class_token) =
676 extract_static_class_before_cursor(line_text, position.character as usize)
677 {
678 if let Some(h) = method_hover_from_index(&class_token, &word, &wi.files) {
679 return Ok(Some(h));
680 }
681 if let Some(resolved_class) =
682 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
683 && let Some(h) = method_hover_from_index(&resolved_class, &word, &wi.files)
684 {
685 return Ok(Some(h));
686 }
687 }
688 }
689 Ok(None)
690 })
691 .await
692 }
693
694 async fn document_symbol(
695 &self,
696 params: DocumentSymbolParams,
697 ) -> Result<Option<DocumentSymbolResponse>> {
698 guard_async_result("document_symbol", async move {
699 let uri = ¶ms.text_document.uri;
700 let doc = match self.get_doc(uri) {
701 Some(d) => d,
702 None => return Ok(None),
703 };
704 Ok(Some(DocumentSymbolResponse::Nested(document_symbols(
705 doc.source(),
706 &doc,
707 ))))
708 })
709 .await
710 }
711
712 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
713 guard_async_result("folding_range", async move {
714 let uri = ¶ms.text_document.uri;
715 let doc = match self.get_doc(uri) {
716 Some(d) => d,
717 None => return Ok(None),
718 };
719 let ranges = folding_ranges(doc.source(), &doc);
720 Ok(if ranges.is_empty() {
721 None
722 } else {
723 Some(ranges)
724 })
725 })
726 .await
727 }
728
729 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
730 guard_async_result("inlay_hint", async move {
731 let uri = ¶ms.text_document.uri;
732 let doc = match self.get_doc(uri) {
733 Some(d) => d,
734 None => return Ok(None),
735 };
736 let analysis = self.cached_analysis_async(uri).await;
737 let wi = self.workspace_index_async().await;
738 Ok(Some(inlay_hints(
739 doc.source(),
740 &doc,
741 analysis.as_deref(),
742 params.range,
743 &wi.files,
744 )))
745 })
746 .await
747 }
748
749 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
750 let fallback = item.clone();
751 let resolved = guard_async("inlay_hint_resolve", async move {
752 if item.tooltip.is_some() {
753 return Some(item);
754 }
755 let func_name = item
756 .data
757 .as_ref()
758 .and_then(|d| d.get("php_lsp_fn"))
759 .and_then(|v| v.as_str())
760 .map(str::to_string);
761 if let Some(name) = func_name {
762 let all_indexes = self.docs.all_indexes();
763 if let Some(md) = docs_for_symbol_from_index(&name, &all_indexes) {
764 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
765 kind: MarkupKind::Markdown,
766 value: md,
767 }));
768 }
769 }
770 Some(item)
771 })
772 .await;
773 Ok(resolved.unwrap_or(fallback))
774 }
775
776 async fn symbol(
777 &self,
778 params: WorkspaceSymbolParams,
779 ) -> Result<Option<Vec<SymbolInformation>>> {
780 guard_async_result("symbol", async move {
781 let wi = self.workspace_index_async().await;
785 let results = workspace_symbols_from_workspace(¶ms.query, &wi);
786 Ok(Some(results))
787 })
788 .await
789 }
790
791 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
792 let docs = self.docs.docs_for(&self.open_urls());
794 let fallback = params.clone();
795 let resolved = guard_async("symbol_resolve", async move {
796 Some(resolve_workspace_symbol(params, &docs))
797 })
798 .await;
799 Ok(resolved.unwrap_or(fallback))
800 }
801
802 #[tracing::instrument(skip_all)]
803 async fn semantic_tokens_full(
804 &self,
805 params: SemanticTokensParams,
806 ) -> Result<Option<SemanticTokensResult>> {
807 guard_async_result("semantic_tokens_full", async move {
808 let uri = ¶ms.text_document.uri;
809 let doc = match self.get_doc(uri) {
810 Some(d) => d,
811 None => {
812 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
813 result_id: None,
814 data: vec![],
815 })));
816 }
817 };
818 let tokens = semantic_tokens(doc.source(), &doc);
819 let result_id = token_hash(&tokens);
820 let tokens_arc = Arc::new(tokens);
821 self.docs
822 .store_token_cache(uri, result_id.clone(), Arc::clone(&tokens_arc));
823 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
824 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
825 result_id: Some(result_id),
826 data,
827 })))
828 })
829 .await
830 }
831
832 async fn semantic_tokens_range(
833 &self,
834 params: SemanticTokensRangeParams,
835 ) -> Result<Option<SemanticTokensRangeResult>> {
836 guard_async_result("semantic_tokens_range", async move {
837 let uri = ¶ms.text_document.uri;
838 let doc = match self.get_doc(uri) {
839 Some(d) => d,
840 None => {
841 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
842 result_id: None,
843 data: vec![],
844 })));
845 }
846 };
847 let tokens = semantic_tokens_range(doc.source(), &doc, params.range);
848 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
849 result_id: None,
850 data: tokens,
851 })))
852 })
853 .await
854 }
855
856 async fn semantic_tokens_full_delta(
857 &self,
858 params: SemanticTokensDeltaParams,
859 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
860 guard_async_result("semantic_tokens_full_delta", 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
867 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
868 let new_result_id = token_hash(&new_tokens);
869 let prev_id = ¶ms.previous_result_id;
870
871 let result = match self.docs.get_token_cache(uri, prev_id) {
872 Some(old_tokens) => {
873 let edits = compute_token_delta(&old_tokens, &new_tokens);
874 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
875 result_id: Some(new_result_id.clone()),
876 edits,
877 })
878 }
879 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
881 result_id: Some(new_result_id.clone()),
882 data: (*new_tokens).clone(),
883 }),
884 };
885
886 self.docs.store_token_cache(uri, new_result_id, new_tokens);
887 Ok(Some(result))
888 })
889 .await
890 }
891
892 async fn selection_range(
893 &self,
894 params: SelectionRangeParams,
895 ) -> Result<Option<Vec<SelectionRange>>> {
896 guard_async_result("selection_range", async move {
897 let uri = ¶ms.text_document.uri;
898 let doc = match self.get_doc(uri) {
899 Some(d) => d,
900 None => return Ok(None),
901 };
902 let ranges = selection_ranges(&doc, ¶ms.positions);
903 Ok(if ranges.is_empty() {
904 None
905 } else {
906 Some(ranges)
907 })
908 })
909 .await
910 }
911
912 async fn prepare_call_hierarchy(
913 &self,
914 params: CallHierarchyPrepareParams,
915 ) -> Result<Option<Vec<CallHierarchyItem>>> {
916 guard_async_result("prepare_call_hierarchy", async move {
917 let uri = ¶ms.text_document_position_params.text_document.uri;
918 let position = params.text_document_position_params.position;
919 let source = self.get_open_text(uri).unwrap_or_default();
920 let word = match word_at_position(&source, position) {
921 Some(w) => w,
922 None => return Ok(None),
923 };
924 let wi = self.workspace_index_async().await;
927 let docs = Arc::clone(&self.docs);
928 let get_doc = move |u: &Url| docs.get_doc_salsa(u);
929 Ok(prepare_call_hierarchy_indexed(&word, &wi, &get_doc).map(|item| vec![item]))
930 })
931 .await
932 }
933
934 async fn incoming_calls(
935 &self,
936 params: CallHierarchyIncomingCallsParams,
937 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
938 guard_async_result("incoming_calls", async move {
939 let docs = Arc::clone(&self.docs);
942 let item = params.item;
943 let calls = tokio::task::spawn_blocking(move || {
944 let all_docs = docs.all_docs_for_scan();
945 incoming_calls(&item, &all_docs)
946 })
947 .await
948 .unwrap_or_default();
949 Ok(if calls.is_empty() { None } else { Some(calls) })
950 })
951 .await
952 }
953
954 async fn outgoing_calls(
955 &self,
956 params: CallHierarchyOutgoingCallsParams,
957 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
958 guard_async_result("outgoing_calls", async move {
959 let wi = self.workspace_index_async().await;
962 let docs = Arc::clone(&self.docs);
963 let item = params.item;
964 let calls = tokio::task::spawn_blocking(move || {
965 let get_doc = |u: &Url| docs.get_doc_salsa(u);
966 outgoing_calls_indexed(&item, &wi, &get_doc)
967 })
968 .await
969 .unwrap_or_default();
970 Ok(if calls.is_empty() { None } else { Some(calls) })
971 })
972 .await
973 }
974
975 async fn document_highlight(
976 &self,
977 params: DocumentHighlightParams,
978 ) -> Result<Option<Vec<DocumentHighlight>>> {
979 guard_async_result("document_highlight", async move {
980 let uri = ¶ms.text_document_position_params.text_document.uri;
981 let position = params.text_document_position_params.position;
982 let source = self.get_open_text(uri).unwrap_or_default();
983 let doc = match self.get_doc(uri) {
984 Some(d) => d,
985 None => return Ok(None),
986 };
987 let highlights = document_highlights(&source, &doc, position);
988 Ok(if highlights.is_empty() {
989 None
990 } else {
991 Some(highlights)
992 })
993 })
994 .await
995 }
996
997 async fn linked_editing_range(
998 &self,
999 params: LinkedEditingRangeParams,
1000 ) -> Result<Option<LinkedEditingRanges>> {
1001 guard_async_result("linked_editing_range", async move {
1002 self.handle_linked_editing_range(params).await
1003 })
1004 .await
1005 }
1006
1007 async fn goto_implementation(
1008 &self,
1009 params: tower_lsp::lsp_types::request::GotoImplementationParams,
1010 ) -> Result<Option<tower_lsp::lsp_types::request::GotoImplementationResponse>> {
1011 guard_async_result("goto_implementation", async move {
1012 let uri = ¶ms.text_document_position_params.text_document.uri;
1013 let position = params.text_document_position_params.position;
1014 let source = self.get_open_text(uri).unwrap_or_default();
1015 let imports = self.file_imports(uri);
1016 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
1017 let (word, fqn_owned): (String, Option<String>) = if raw_word.contains('\\') {
1022 let short = raw_word
1023 .rsplit('\\')
1024 .next()
1025 .unwrap_or(&raw_word)
1026 .to_string();
1027 let full = raw_word.trim_start_matches('\\').to_string();
1028 (short, Some(full))
1029 } else {
1030 let fqn = imports.get(&raw_word).cloned();
1031 (raw_word, fqn)
1032 };
1033 let fqn = fqn_owned.as_deref();
1034 let open_docs = self.docs.docs_for(&self.open_urls());
1036 let mut locs = find_implementations(&word, fqn, &open_docs);
1037 let wi = self.workspace_index_async().await;
1039 if locs.is_empty() {
1040 locs = find_implementations_from_workspace(&word, fqn, &wi);
1041 }
1042 if locs.is_empty()
1046 && let Some(doc) = self.get_doc(uri)
1047 && let Some(enclosing) =
1048 crate::types::type_map::enclosing_class_at(&source, &doc, position)
1049 {
1050 locs = find_method_implementations_from_workspace(&word, &enclosing, &wi);
1051 }
1052 if locs.is_empty() {
1053 Ok(None)
1054 } else {
1055 Ok(Some(GotoDefinitionResponse::Array(locs)))
1056 }
1057 })
1058 .await
1059 }
1060
1061 async fn goto_declaration(
1062 &self,
1063 params: tower_lsp::lsp_types::request::GotoDeclarationParams,
1064 ) -> Result<Option<tower_lsp::lsp_types::request::GotoDeclarationResponse>> {
1065 guard_async_result("goto_declaration", async move {
1066 let uri = ¶ms.text_document_position_params.text_document.uri;
1067 let position = params.text_document_position_params.position;
1068 let source = self.get_open_text(uri).unwrap_or_default();
1069 let open_docs = self.docs.docs_for(&self.open_urls());
1071 if let Some(loc) = goto_declaration(&source, &open_docs, position) {
1072 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1073 }
1074 let all_indexes = self.docs.all_indexes();
1076 Ok(goto_declaration_from_index(&source, &all_indexes, position)
1077 .map(GotoDefinitionResponse::Scalar))
1078 })
1079 .await
1080 }
1081
1082 async fn goto_type_definition(
1083 &self,
1084 params: tower_lsp::lsp_types::request::GotoTypeDefinitionParams,
1085 ) -> Result<Option<tower_lsp::lsp_types::request::GotoTypeDefinitionResponse>> {
1086 guard_async_result("goto_type_definition", 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 doc = match self.get_doc(uri) {
1091 Some(d) => d,
1092 None => return Ok(None),
1093 };
1094 let analysis = self.cached_analysis_async(uri).await;
1095 let open_docs = self.docs.docs_for(&self.open_urls());
1097 let mut results =
1098 goto_type_definition(&source, &doc, analysis.as_deref(), &open_docs, position);
1099
1100 if results.is_empty() {
1102 let all_indexes = self.docs.all_indexes();
1103 results = goto_type_definition_from_index(
1104 &source,
1105 &doc,
1106 analysis.as_deref(),
1107 &all_indexes,
1108 position,
1109 );
1110 }
1111
1112 let response = match results.len() {
1114 0 => None,
1115 1 => Some(GotoDefinitionResponse::Scalar(
1116 results.into_iter().next().unwrap(),
1117 )),
1118 _ => Some(GotoDefinitionResponse::Array(results)),
1119 };
1120 Ok(response)
1121 })
1122 .await
1123 }
1124
1125 async fn prepare_type_hierarchy(
1126 &self,
1127 params: TypeHierarchyPrepareParams,
1128 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1129 guard_async_result("prepare_type_hierarchy", async move {
1130 let uri = ¶ms.text_document_position_params.text_document.uri;
1131 let position = params.text_document_position_params.position;
1132 let source = self.get_open_text(uri).unwrap_or_default();
1133 let wi = self.workspace_index_async().await;
1135 Ok(
1136 prepare_type_hierarchy_from_workspace(&source, &wi, position)
1137 .map(|item| vec![item]),
1138 )
1139 })
1140 .await
1141 }
1142
1143 async fn supertypes(
1144 &self,
1145 params: TypeHierarchySupertypesParams,
1146 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1147 guard_async_result("supertypes", async move {
1148 let wi = self.workspace_index_async().await;
1152 let loaded_new = self
1153 .ensure_direct_supertypes_loaded(¶ms.item.name, &wi)
1154 .await;
1155 let wi = if loaded_new {
1156 self.workspace_index_async().await
1157 } else {
1158 wi
1159 };
1160 let result = supertypes_of_from_workspace(¶ms.item, &wi);
1161 Ok(if result.is_empty() {
1162 None
1163 } else {
1164 Some(result)
1165 })
1166 })
1167 .await
1168 }
1169
1170 async fn subtypes(
1171 &self,
1172 params: TypeHierarchySubtypesParams,
1173 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1174 guard_async_result("subtypes", async move {
1175 let wi = self.workspace_index_async().await;
1177 let result = subtypes_of_from_workspace(¶ms.item, &wi);
1178 Ok(if result.is_empty() {
1179 None
1180 } else {
1181 Some(result)
1182 })
1183 })
1184 .await
1185 }
1186
1187 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
1188 guard_async_result("code_lens", async move {
1189 let uri = ¶ms.text_document.uri;
1190 let doc = match self.get_doc(uri) {
1191 Some(d) => d,
1192 None => return Ok(None),
1193 };
1194 let docs = Arc::clone(&self.docs);
1197 let uri_owned = uri.clone();
1198 let lenses = tokio::task::spawn_blocking(move || {
1199 let all_docs = docs.all_docs_for_scan();
1200 code_lenses(&uri_owned, &doc, &all_docs)
1201 })
1202 .await
1203 .unwrap_or_default();
1204 Ok(if lenses.is_empty() {
1205 None
1206 } else {
1207 Some(lenses)
1208 })
1209 })
1210 .await
1211 }
1212
1213 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
1214 let fallback = params.clone();
1215 let resolved = guard_async("code_lens_resolve", async move {
1216 Some(params)
1218 })
1219 .await;
1220 Ok(resolved.unwrap_or(fallback))
1221 }
1222
1223 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
1224 guard_async_result("document_link", async move {
1225 let uri = ¶ms.text_document.uri;
1226 let doc = match self.get_doc(uri) {
1227 Some(d) => d,
1228 None => return Ok(None),
1229 };
1230 let links = document_links(uri, &doc, doc.source());
1231 Ok(if links.is_empty() { None } else { Some(links) })
1232 })
1233 .await
1234 }
1235
1236 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
1237 let fallback = params.clone();
1238 let resolved = guard_async("document_link_resolve", async move {
1239 Some(params)
1241 })
1242 .await;
1243 Ok(resolved.unwrap_or(fallback))
1244 }
1245
1246 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
1247 guard_async_result("formatting", async move {
1248 let uri = ¶ms.text_document.uri;
1249 let source = self.get_open_text(uri).unwrap_or_default();
1250 Ok(format_document(&source))
1251 })
1252 .await
1253 }
1254
1255 async fn range_formatting(
1256 &self,
1257 params: DocumentRangeFormattingParams,
1258 ) -> Result<Option<Vec<TextEdit>>> {
1259 guard_async_result("range_formatting", async move {
1260 let uri = ¶ms.text_document.uri;
1261 let source = self.get_open_text(uri).unwrap_or_default();
1262 Ok(format_range(&source, params.range))
1263 })
1264 .await
1265 }
1266
1267 async fn on_type_formatting(
1268 &self,
1269 params: DocumentOnTypeFormattingParams,
1270 ) -> Result<Option<Vec<TextEdit>>> {
1271 guard_async_result("on_type_formatting", async move {
1272 let uri = ¶ms.text_document_position.text_document.uri;
1273 let source = self.get_open_text(uri).unwrap_or_default();
1274 let edits = on_type_format(
1275 &source,
1276 params.text_document_position.position,
1277 ¶ms.ch,
1278 ¶ms.options,
1279 );
1280 Ok(if edits.is_empty() { None } else { Some(edits) })
1281 })
1282 .await
1283 }
1284
1285 async fn execute_command(
1286 &self,
1287 params: ExecuteCommandParams,
1288 ) -> Result<Option<serde_json::Value>> {
1289 guard_async_result("execute_command", async move {
1290 match params.command.as_str() {
1291 "php-lsp.runTest" => {
1292 let file_uri = params
1294 .arguments
1295 .first()
1296 .and_then(|v| v.as_str())
1297 .and_then(|s| Url::parse(s).ok());
1298 let filter = params
1299 .arguments
1300 .get(1)
1301 .and_then(|v| v.as_str())
1302 .unwrap_or("")
1303 .to_string();
1304
1305 let root = self.root_paths.load().first().cloned();
1306 let client = self.client.clone();
1307
1308 tokio::spawn(async move {
1309 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
1310 });
1311
1312 Ok(None)
1313 }
1314 _ => Ok(None),
1315 }
1316 })
1317 .await
1318 }
1319
1320 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
1321 guard_async_result("will_rename_files", async move {
1322 self.handle_will_rename_files(params).await
1323 })
1324 .await
1325 }
1326
1327 async fn did_rename_files(&self, params: RenameFilesParams) {
1328 guard_async("did_rename_files", async move {
1329 self.handle_did_rename_files(params).await
1330 })
1331 .await
1332 }
1333
1334 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
1335 guard_async_result("will_create_files", async move {
1336 self.handle_will_create_files(params).await
1337 })
1338 .await
1339 }
1340
1341 async fn did_create_files(&self, params: CreateFilesParams) {
1342 guard_async("did_create_files", async move {
1343 self.handle_did_create_files(params).await
1344 })
1345 .await
1346 }
1347
1348 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
1351 guard_async_result("will_delete_files", async move {
1352 self.handle_will_delete_files(params).await
1353 })
1354 .await
1355 }
1356
1357 async fn did_delete_files(&self, params: DeleteFilesParams) {
1358 guard_async("did_delete_files", async move {
1359 self.handle_did_delete_files(params).await
1360 })
1361 .await
1362 }
1363
1364 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
1365 guard_async_result("moniker", async move {
1366 let uri = ¶ms.text_document_position_params.text_document.uri;
1367 let position = params.text_document_position_params.position;
1368 let source = self.get_open_text(uri).unwrap_or_default();
1369 let doc = match self.get_doc(uri) {
1370 Some(d) => d,
1371 None => return Ok(None),
1372 };
1373 let imports = self.file_imports(uri);
1374 Ok(moniker_at(&source, &doc, position, &imports).map(|m| vec![m]))
1375 })
1376 .await
1377 }
1378
1379 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
1380 guard_async_result("inline_value", async move {
1381 let uri = ¶ms.text_document.uri;
1382 let source = self.get_open_text(uri).unwrap_or_default();
1383 let values = inline_values_in_range(&source, params.range);
1384 Ok(if values.is_empty() {
1385 None
1386 } else {
1387 Some(values)
1388 })
1389 })
1390 .await
1391 }
1392
1393 async fn diagnostic(
1394 &self,
1395 params: DocumentDiagnosticParams,
1396 ) -> Result<DocumentDiagnosticReportResult> {
1397 let report = guard_async("diagnostic", async move {
1398 Some(self.handle_diagnostic(params).await)
1399 })
1400 .await;
1401 match report {
1402 Some(r) => r,
1403 None => Ok(DocumentDiagnosticReportResult::Report(
1404 DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
1405 )),
1406 }
1407 }
1408
1409 async fn workspace_diagnostic(
1410 &self,
1411 params: WorkspaceDiagnosticParams,
1412 ) -> Result<WorkspaceDiagnosticReportResult> {
1413 let report = guard_async("workspace_diagnostic", async move {
1414 Some(self.handle_workspace_diagnostic(params).await)
1415 })
1416 .await;
1417 match report {
1418 Some(r) => r,
1419 None => Ok(WorkspaceDiagnosticReportResult::Report(
1420 WorkspaceDiagnosticReport::default(),
1421 )),
1422 }
1423 }
1424
1425 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
1426 guard_async_result("code_action", async move {
1427 self.handle_code_action(params).await
1428 })
1429 .await
1430 }
1431
1432 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
1433 guard_async_result("code_action_resolve", async move {
1434 self.handle_code_action_resolve(item).await
1435 })
1436 .await
1437 }
1438}