1#![allow(unused_imports)]
2
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use arc_swap::ArcSwap;
7
8use tower_lsp::jsonrpc::Result;
9use tower_lsp::lsp_types::notification::Progress as ProgressNotification;
10use tower_lsp::lsp_types::request::WorkDoneProgressCreate;
11use tower_lsp::lsp_types::*;
12use tower_lsp::{Client, LanguageServer, async_trait};
13
14use php_ast::{
15 ClassMember, ClassMemberKind, EnumMember, EnumMemberKind, ExprKind, NamespaceBody, Stmt,
16 StmtKind,
17};
18
19use super::panic_guard::{guard_async, guard_async_result};
20use crate::completion::{CompletionCtx, filtered_completions_at};
21use crate::document::ast::{ParsedDoc, str_offset};
22use crate::document::document_store::DocumentStore;
23use crate::document::open_files::{OpenFiles, compute_open_file_diagnostics};
24use crate::editing::file_rename::{use_edits_for_delete, use_edits_for_rename};
25use crate::editing::use_import::{build_use_import_edit, find_fqn_for_class};
26use crate::hover::{
27 class_hover_from_index, docs_for_symbol_from_index, extract_static_class_before_cursor,
28 hover_info_with_maps, method_hover_from_index, signature_for_symbol_from_index,
29};
30use crate::index::workspace_scan::{scan_workspace, send_refresh_requests};
31use crate::lang::autoload::Psr4Map;
32use crate::lang::config::LspConfig;
33use crate::lang::phpstorm_meta::PhpStormMeta;
34use crate::navigation::symbols::{
35 document_symbols, resolve_workspace_symbol, workspace_symbols_from_workspace,
36};
37use crate::text::{fqn_short_name, word_at_position};
38
39use crate::actions::extract_action::extract_variable_actions;
40use crate::actions::extract_constant_action::extract_constant_actions;
41use crate::actions::extract_method_action::extract_method_actions;
42use crate::actions::generate_action::{
43 generate_constructor_actions, generate_getters_setters_actions,
44};
45use crate::actions::implement_action::implement_missing_actions;
46use crate::actions::inline_action::inline_variable_actions;
47use crate::actions::phpdoc_action::phpdoc_actions;
48use crate::actions::promote_action::promote_constructor_actions;
49use crate::actions::type_action::add_return_type_actions;
50
51use crate::navigation::call_hierarchy::{
52 incoming_calls, outgoing_calls_indexed, prepare_call_hierarchy_indexed,
53};
54use crate::navigation::declaration::{goto_declaration, goto_declaration_from_index};
55use crate::navigation::definition::{
56 find_declaration_range, find_method_in_class_hierarchy, find_method_range_in_class,
57 goto_definition,
58};
59use crate::navigation::implementation::{
60 find_implementations, find_implementations_from_workspace,
61 find_method_implementations_from_workspace,
62};
63use crate::navigation::moniker::moniker_at;
64use crate::navigation::references::{
65 SymbolKind, find_constructor_references, find_references, find_references_with_target,
66};
67use crate::navigation::type_definition::{goto_type_definition, goto_type_definition_from_index};
68use crate::navigation::type_hierarchy::{
69 prepare_type_hierarchy_from_workspace, subtypes_of_from_workspace, supertypes_of_from_workspace,
70};
71
72use crate::analysis::code_lens::code_lenses;
73use crate::analysis::diagnostics::{
74 diagnostics_from_doc, merge_file_diagnostics, parse_document, parse_document_no_diags,
75};
76use crate::analysis::document_highlight::document_highlights;
77use crate::analysis::inlay_hints::inlay_hints;
78use crate::analysis::inline_value::inline_values_in_range;
79use crate::analysis::semantic_tokens::{
80 compute_token_delta, legend, semantic_tokens, semantic_tokens_range, token_hash,
81};
82
83use crate::editing::document_link::document_links;
84use crate::editing::folding::folding_ranges;
85use crate::editing::formatting::{format_document, format_range};
86use crate::editing::on_type_format::on_type_format;
87use crate::editing::organize_imports::organize_imports_action;
88use crate::editing::rename::{prepare_rename, rename, rename_property, rename_variable};
89use crate::editing::selection_range::selection_ranges;
90use crate::editing::signature_help::signature_help;
91
92use super::helpers::{
93 DEFERRED_ACTION_TAGS, class_name_at_construct_decl, cursor_is_on_constant_decl,
94 cursor_is_on_method_decl, cursor_is_on_property_decl, defer_actions, is_after_arrow,
95 php_file_op, promoted_property_at_cursor, range_within, run_phpunit, symbol_kind_at,
96};
97use super::{
98 Backend, IndexReadyNotification, build_mir_symbol, compute_dependent_publishes_owned,
99 compute_diagnostic_result_id, publish_with_dependents, resolve_reference_symbol,
100};
101
102#[async_trait]
103impl LanguageServer for Backend {
104 async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
105 self.handle_initialize(params).await
106 }
107
108 async fn initialized(&self, _params: InitializedParams) {
109 self.handle_initialized(_params).await
110 }
111
112 async fn did_change_configuration(&self, _params: DidChangeConfigurationParams) {
113 let items = vec![ConfigurationItem {
116 scope_uri: None,
117 section: Some("php-lsp".to_string()),
118 }];
119 if let Ok(values) = self.client.configuration(items).await
120 && let Some(value) = values.into_iter().next()
121 {
122 let roots = self.root_paths.load_full();
123
124 let file_cfg = crate::lang::autoload::load_project_config_json(&roots);
127
128 if let Some(ver) = value.get("phpVersion").and_then(|v| v.as_str())
129 && !crate::lang::autoload::is_valid_php_version(ver)
130 {
131 self.client
132 .log_message(
133 tower_lsp::lsp_types::MessageType::WARNING,
134 format!(
135 "php-lsp: unsupported phpVersion {ver:?} — valid values: {}",
136 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
137 ),
138 )
139 .await;
140 }
141
142 let file_obj = file_cfg.as_ref().filter(|v| v.is_object());
143 let merged = LspConfig::merge_project_configs(file_obj, Some(&value));
144 let mut cfg = LspConfig::from_value(&merged);
145
146 let (ver, source) = self.resolve_php_version(cfg.php_version.as_deref());
148 self.client
149 .log_message(
150 tower_lsp::lsp_types::MessageType::INFO,
151 format!("php-lsp: using PHP {ver} ({source})"),
152 )
153 .await;
154 let ver = if source != "set by editor"
156 && !crate::lang::autoload::is_valid_php_version(&ver)
157 {
158 let clamped = crate::lang::autoload::clamp_php_version(&ver);
159 self.client
160 .show_message(
161 tower_lsp::lsp_types::MessageType::WARNING,
162 format!(
163 "php-lsp: detected PHP {ver} is outside the supported range ({}); \
164 using PHP {clamped} for analysis",
165 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
166 ),
167 )
168 .await;
169 clamped.to_string()
170 } else {
171 ver
172 };
173 cfg.php_version = Some(ver.clone());
174 if let Ok(pv) = ver.parse::<mir_analyzer::PhpVersion>() {
175 self.docs.set_php_version(pv);
176 }
177 self.config.store(Arc::new(cfg));
178 send_refresh_requests(&self.client).await;
179 }
180 }
181
182 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
183 {
185 let mut roots = (**self.root_paths.load()).clone();
186 for removed in ¶ms.event.removed {
187 if let Ok(path) = removed.uri.to_file_path() {
188 roots.retain(|r| r != &path);
189 }
190 }
191 self.root_paths.store(Arc::new(roots));
192 }
193
194 let (exclude_paths, include_paths, max_indexed_files) = {
196 let cfg = self.config.load();
197 (
198 cfg.exclude_paths.clone(),
199 cfg.include_paths.clone(),
200 cfg.max_indexed_files,
201 )
202 };
203 for added in ¶ms.event.added {
204 if let Ok(path) = added.uri.to_file_path() {
205 let is_new = {
206 let mut roots = (**self.root_paths.load()).clone();
207 if !roots.contains(&path) {
208 roots.push(path.clone());
209 self.root_paths.store(Arc::new(roots));
210 true
211 } else {
212 false
213 }
214 };
215 if is_new {
216 let docs = Arc::clone(&self.docs);
217 let open_files = self.open_files.clone();
218 let ex = exclude_paths.clone();
219 let ip = include_paths.clone();
220 let path_clone = path.clone();
221 let client = self.client.clone();
222 tokio::spawn(async move {
223 let cache = crate::index::cache::WorkspaceCache::new(&path_clone);
224 scan_workspace(
225 path_clone,
226 docs,
227 open_files,
228 cache,
229 &ex,
230 &ip,
231 max_indexed_files,
232 )
233 .await;
234 send_refresh_requests(&client).await;
235 });
236 }
237 }
238 }
239 }
240
241 async fn shutdown(&self) -> Result<()> {
242 Ok(())
243 }
244
245 #[tracing::instrument(skip_all)]
246 async fn did_open(&self, params: DidOpenTextDocumentParams) {
247 guard_async("did_open", async move {
248 let uri = params.text_document.uri;
249 let text = params.text_document.text;
250
251 self.set_open_text(uri.clone(), text);
255
256 let parse_diags = self
259 .docs
260 .get_doc_salsa(&uri)
261 .map(|doc| diagnostics_from_doc(&doc))
262 .unwrap_or_default();
263 self.set_parse_diagnostics(&uri, parse_diags);
264
265 publish_with_dependents(
266 self.client.clone(),
267 Arc::clone(&self.docs),
268 self.open_files.clone(),
269 uri,
270 self.config.load().diagnostics.clone(),
271 )
272 .await;
273 })
274 .await
275 }
276
277 #[tracing::instrument(skip_all)]
278 async fn did_change(&self, params: DidChangeTextDocumentParams) {
279 guard_async("did_change", async move {
280 let uri = params.text_document.uri;
281 let mut updated: Option<String> = None;
286 for change in params.content_changes {
287 match change.range {
288 None => updated = Some(change.text),
289 Some(range) => {
290 let mut cur = match updated.take() {
291 Some(t) => t,
292 None => self.get_open_text(&uri).unwrap_or_default(),
293 };
294 crate::text::apply_content_change(&mut cur, range, &change.text);
295 updated = Some(cur);
296 }
297 }
298 }
299 let Some(text) = updated else { return };
300
301 let version = self.set_open_text(uri.clone(), text.clone());
305
306 let docs = Arc::clone(&self.docs);
307 let open_files = self.open_files.clone();
308 let client = self.client.clone();
309 let diag_cfg = self.config.load().diagnostics.clone();
310 tokio::spawn(async move {
311 tokio::time::sleep(std::time::Duration::from_millis(100)).await;
314
315 let (_doc, parse_diags) =
316 tokio::task::spawn_blocking(move || parse_document(&text))
317 .await
318 .unwrap_or_else(|_| (ParsedDoc::default(), vec![]));
319
320 if open_files.current_version(&uri) == Some(version) {
322 open_files.set_parse_diagnostics(&uri, parse_diags);
323 publish_with_dependents(client, docs, open_files, uri, diag_cfg).await;
324 }
325 });
326 })
327 .await
328 }
329
330 async fn did_close(&self, params: DidCloseTextDocumentParams) {
331 let uri = params.text_document.uri;
332 self.close_open_file(&uri);
333 self.client.publish_diagnostics(uri, vec![], None).await;
335 }
336
337 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
338
339 async fn will_save_wait_until(
340 &self,
341 params: WillSaveTextDocumentParams,
342 ) -> Result<Option<Vec<TextEdit>>> {
343 let source = self
344 .get_open_text(¶ms.text_document.uri)
345 .unwrap_or_default();
346 Ok(format_document(&source))
347 }
348
349 async fn did_save(&self, params: DidSaveTextDocumentParams) {
350 let uri = params.text_document.uri;
351 let diag_cfg = self.config.load().diagnostics.clone();
357 let all = compute_open_file_diagnostics(&self.docs, &self.open_files, &uri, &diag_cfg);
358 self.client
359 .publish_diagnostics(uri.clone(), all, None)
360 .await;
361
362 if let (Some(root), Ok(path)) =
374 (self.root_paths.load().first().cloned(), uri.to_file_path())
375 {
376 tokio::task::spawn_blocking(move || {
377 let Some(cache) = crate::index::cache::WorkspaceCache::new(&root) else {
378 return;
379 };
380 let Ok(meta) = std::fs::metadata(&path) else {
383 return;
384 };
385 let Ok(text) = std::fs::read_to_string(&path) else {
386 return;
387 };
388 let mtime_secs = meta
389 .modified()
390 .ok()
391 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
392 .map(|d| d.as_secs())
393 .unwrap_or(0);
394 let key = crate::index::cache::WorkspaceCache::key_for_stat(
395 uri.as_str(),
396 mtime_secs,
397 meta.len(),
398 );
399 let doc = parse_document_no_diags(&text);
400 let index = crate::index::file_index::FileIndex::extract(&doc);
401 let _ = cache.write(&key, &index);
402 });
403 }
404 }
405
406 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
407 for change in params.changes {
408 match change.typ {
409 FileChangeType::CREATED | FileChangeType::CHANGED => {
410 if let Ok(path) = change.uri.to_file_path()
411 && let Ok(text) = tokio::fs::read_to_string(&path).await
412 {
413 let doc = parse_document_no_diags(&text);
418 self.ingest_from_doc_if_not_open(change.uri.clone(), &doc);
419 }
420 }
421 FileChangeType::DELETED => {
422 self.docs.remove(&change.uri);
423 }
424 _ => {}
425 }
426 }
427 send_refresh_requests(&self.client).await;
429 }
430
431 #[tracing::instrument(skip_all)]
432 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
433 guard_async_result("completion", async move {
434 let uri = ¶ms.text_document_position.text_document.uri;
435 let position = params.text_document_position.position;
436 let source = self.get_open_text(uri).unwrap_or_default();
437 let doc = match self.get_doc(uri) {
438 Some(d) => d,
439 None => return Ok(Some(CompletionResponse::Array(vec![]))),
440 };
441 let other_docs: Vec<Arc<ParsedDoc>> = self
442 .docs
443 .other_docs(uri, &self.open_urls())
444 .into_iter()
445 .map(|(_, d)| d)
446 .collect();
447 let trigger = params
448 .context
449 .as_ref()
450 .and_then(|c| c.trigger_character.as_deref());
451 let meta_loaded = self.meta.load();
452 let meta_opt = if meta_loaded.is_empty() {
453 None
454 } else {
455 Some(&**meta_loaded)
456 };
457 let imports = self.file_imports(uri);
458 let wi = self.workspace_index_async().await;
459 let docs_for_lookup = Arc::clone(&self.docs);
460 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
461 let cr = *wi.classes_by_name.get(name)?.first()?;
462 let (uri, _) = wi.at(cr)?;
463 docs_for_lookup.get_doc_salsa(uri)
464 };
465 let analysis = self.cached_analysis_async(uri).await;
466 let docs_for_tm = Arc::clone(&self.docs);
470 let doc_for_tm = Arc::clone(&doc);
471 let uri_for_tm = uri.clone();
472 let get_type_map =
473 move || docs_for_tm.cached_type_map(&uri_for_tm, &doc_for_tm, meta_opt);
474 let session = self
475 .docs
476 .analysis_session(self.docs.workspace_php_version());
477 let ctx = CompletionCtx {
478 source: Some(&source),
479 position: Some(position),
480 meta: meta_opt,
481 doc_uri: Some(uri),
482 file_imports: Some(&imports),
483 find_class_doc: Some(&find_class_doc_fn),
484 analysis: analysis.as_deref(),
485 type_map: Some(&get_type_map),
486 session: Some(session),
487 };
488 Ok(Some(CompletionResponse::Array(filtered_completions_at(
489 &doc,
490 &other_docs,
491 trigger,
492 &ctx,
493 ))))
494 })
495 .await
496 }
497
498 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
499 if item.documentation.is_some() && item.detail.is_some() {
500 return Ok(item);
501 }
502 let name = item.label.trim_end_matches(':');
504 let all_indexes = self.docs.all_indexes();
505 if item.detail.is_none()
506 && let Some(sig) = signature_for_symbol_from_index(name, &all_indexes)
507 {
508 item.detail = Some(sig);
509 }
510 if item.documentation.is_none()
511 && let Some(md) = docs_for_symbol_from_index(name, &all_indexes)
512 {
513 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
514 kind: MarkupKind::Markdown,
515 value: md,
516 }));
517 }
518 Ok(item)
519 }
520
521 async fn goto_definition(
522 &self,
523 params: GotoDefinitionParams,
524 ) -> Result<Option<GotoDefinitionResponse>> {
525 self.handle_goto_definition(params).await
526 }
527
528 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
529 self.handle_references(params).await
530 }
531
532 async fn prepare_rename(
533 &self,
534 params: TextDocumentPositionParams,
535 ) -> Result<Option<PrepareRenameResponse>> {
536 let uri = ¶ms.text_document.uri;
537 let source = self.get_open_text(uri).unwrap_or_default();
538 Ok(prepare_rename(&source, params.position).map(PrepareRenameResponse::Range))
539 }
540
541 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
542 let uri = ¶ms.text_document_position.text_document.uri;
543 let position = params.text_document_position.position;
544 let source = self.get_open_text(uri).unwrap_or_default();
545 let word = match word_at_position(&source, position) {
546 Some(w) => w,
547 None => return Ok(None),
548 };
549 if word.starts_with('$') {
550 let doc = match self.get_doc(uri) {
551 Some(d) => d,
552 None => return Ok(None),
553 };
554 let prop_name = cursor_is_on_property_decl(&source, &doc.program().stmts, position)
558 .or_else(|| promoted_property_at_cursor(&source, &doc.program().stmts, position));
559 if let Some(prop_name) = prop_name {
560 let all_docs = self.docs.all_docs_for_scan();
561 return Ok(Some(rename_property(
562 &prop_name,
563 ¶ms.new_name,
564 &all_docs,
565 )));
566 }
567 Ok(Some(rename_variable(
568 &word,
569 ¶ms.new_name,
570 uri,
571 &doc,
572 position,
573 )))
574 } else if is_after_arrow(&source, position) {
575 let all_docs = self.docs.all_docs_for_scan();
576 Ok(Some(rename_property(&word, ¶ms.new_name, &all_docs)))
577 } else {
578 let all_docs = self.docs.all_docs_for_scan();
579 let doc_opt = self.get_doc(uri);
580 let target_fqn: Option<String> = doc_opt.as_ref().map(|doc| {
581 let imports = self.file_imports(uri);
582 crate::navigation::moniker::resolve_fqn(doc, &word, &imports)
583 });
584 Ok(Some(rename(
585 &word,
586 ¶ms.new_name,
587 &all_docs,
588 target_fqn.as_deref(),
589 )))
590 }
591 }
592
593 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
594 let uri = ¶ms.text_document_position_params.text_document.uri;
595 let position = params.text_document_position_params.position;
596 let source = self.get_open_text(uri).unwrap_or_default();
597 let doc = match self.get_doc(uri) {
598 Some(d) => d,
599 None => return Ok(None),
600 };
601 let all_indexes = self.docs.all_indexes();
602 Ok(signature_help(&source, &doc, position, &all_indexes))
603 }
604
605 #[tracing::instrument(skip_all)]
606 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
607 guard_async_result("hover", async move {
608 let uri = ¶ms.text_document_position_params.text_document.uri;
609 let position = params.text_document_position_params.position;
610 let source = self.get_open_text(uri).unwrap_or_default();
611 let doc = match self.get_doc(uri) {
612 Some(d) => d,
613 None => return Ok(None),
614 };
615 let other_docs = self.docs.other_docs(uri, &self.open_urls());
616 let other_maps = self.docs.other_symbol_maps(uri, &self.open_urls());
617 let analysis = self.cached_analysis_async(uri).await;
618 let hover_session = self
619 .docs
620 .analysis_session(self.docs.workspace_php_version());
621 let result = hover_info_with_maps(
622 &source,
623 &doc,
624 analysis.as_deref(),
625 position,
626 &other_docs,
627 &other_maps,
628 Some(&hover_session),
629 );
630 if result.is_some() {
631 return Ok(result);
632 }
633 if let Some(word) = crate::text::word_at_position(&source, position) {
638 let wi = self.workspace_index_async().await;
639 if let Some(h) = class_hover_from_index(&word, &wi.files) {
641 return Ok(Some(h));
642 }
643 if let Some(resolved) = crate::hover::resolve_use_alias(&doc.program().stmts, &word)
645 && let Some(h) = class_hover_from_index(&resolved, &wi.files)
646 {
647 return Ok(Some(h));
648 }
649 if let Some(line_text) = source.lines().nth(position.line as usize)
651 && let Some(class_token) =
652 extract_static_class_before_cursor(line_text, position.character as usize)
653 {
654 if let Some(h) = method_hover_from_index(&class_token, &word, &wi.files) {
655 return Ok(Some(h));
656 }
657 if let Some(resolved_class) =
658 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
659 && let Some(h) = method_hover_from_index(&resolved_class, &word, &wi.files)
660 {
661 return Ok(Some(h));
662 }
663 }
664 }
665 Ok(None)
666 })
667 .await
668 }
669
670 async fn document_symbol(
671 &self,
672 params: DocumentSymbolParams,
673 ) -> Result<Option<DocumentSymbolResponse>> {
674 let uri = ¶ms.text_document.uri;
675 let doc = match self.get_doc(uri) {
676 Some(d) => d,
677 None => return Ok(None),
678 };
679 Ok(Some(DocumentSymbolResponse::Nested(document_symbols(
680 doc.source(),
681 &doc,
682 ))))
683 }
684
685 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
686 let uri = ¶ms.text_document.uri;
687 let doc = match self.get_doc(uri) {
688 Some(d) => d,
689 None => return Ok(None),
690 };
691 let ranges = folding_ranges(doc.source(), &doc);
692 Ok(if ranges.is_empty() {
693 None
694 } else {
695 Some(ranges)
696 })
697 }
698
699 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
700 let uri = ¶ms.text_document.uri;
701 let doc = match self.get_doc(uri) {
702 Some(d) => d,
703 None => return Ok(None),
704 };
705 let analysis = self.cached_analysis_async(uri).await;
706 let wi = self.workspace_index_async().await;
707 Ok(Some(inlay_hints(
708 doc.source(),
709 &doc,
710 analysis.as_deref(),
711 params.range,
712 &wi.files,
713 )))
714 }
715
716 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
717 if item.tooltip.is_some() {
718 return Ok(item);
719 }
720 let func_name = item
721 .data
722 .as_ref()
723 .and_then(|d| d.get("php_lsp_fn"))
724 .and_then(|v| v.as_str())
725 .map(str::to_string);
726 if let Some(name) = func_name {
727 let all_indexes = self.docs.all_indexes();
728 if let Some(md) = docs_for_symbol_from_index(&name, &all_indexes) {
729 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
730 kind: MarkupKind::Markdown,
731 value: md,
732 }));
733 }
734 }
735 Ok(item)
736 }
737
738 async fn symbol(
739 &self,
740 params: WorkspaceSymbolParams,
741 ) -> Result<Option<Vec<SymbolInformation>>> {
742 let wi = self.workspace_index_async().await;
746 let results = workspace_symbols_from_workspace(¶ms.query, &wi);
747 Ok(Some(results))
748 }
749
750 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
751 let docs = self.docs.docs_for(&self.open_urls());
753 Ok(resolve_workspace_symbol(params, &docs))
754 }
755
756 #[tracing::instrument(skip_all)]
757 async fn semantic_tokens_full(
758 &self,
759 params: SemanticTokensParams,
760 ) -> Result<Option<SemanticTokensResult>> {
761 guard_async_result("semantic_tokens_full", async move {
762 let uri = ¶ms.text_document.uri;
763 let doc = match self.get_doc(uri) {
764 Some(d) => d,
765 None => {
766 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
767 result_id: None,
768 data: vec![],
769 })));
770 }
771 };
772 let tokens = semantic_tokens(doc.source(), &doc);
773 let result_id = token_hash(&tokens);
774 let tokens_arc = Arc::new(tokens);
775 self.docs
776 .store_token_cache(uri, result_id.clone(), Arc::clone(&tokens_arc));
777 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
778 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
779 result_id: Some(result_id),
780 data,
781 })))
782 })
783 .await
784 }
785
786 async fn semantic_tokens_range(
787 &self,
788 params: SemanticTokensRangeParams,
789 ) -> Result<Option<SemanticTokensRangeResult>> {
790 let uri = ¶ms.text_document.uri;
791 let doc = match self.get_doc(uri) {
792 Some(d) => d,
793 None => {
794 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
795 result_id: None,
796 data: vec![],
797 })));
798 }
799 };
800 let tokens = semantic_tokens_range(doc.source(), &doc, params.range);
801 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
802 result_id: None,
803 data: tokens,
804 })))
805 }
806
807 async fn semantic_tokens_full_delta(
808 &self,
809 params: SemanticTokensDeltaParams,
810 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
811 let uri = ¶ms.text_document.uri;
812 let doc = match self.get_doc(uri) {
813 Some(d) => d,
814 None => return Ok(None),
815 };
816
817 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
818 let new_result_id = token_hash(&new_tokens);
819 let prev_id = ¶ms.previous_result_id;
820
821 let result = match self.docs.get_token_cache(uri, prev_id) {
822 Some(old_tokens) => {
823 let edits = compute_token_delta(&old_tokens, &new_tokens);
824 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
825 result_id: Some(new_result_id.clone()),
826 edits,
827 })
828 }
829 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
831 result_id: Some(new_result_id.clone()),
832 data: (*new_tokens).clone(),
833 }),
834 };
835
836 self.docs.store_token_cache(uri, new_result_id, new_tokens);
837 Ok(Some(result))
838 }
839
840 async fn selection_range(
841 &self,
842 params: SelectionRangeParams,
843 ) -> Result<Option<Vec<SelectionRange>>> {
844 let uri = ¶ms.text_document.uri;
845 let doc = match self.get_doc(uri) {
846 Some(d) => d,
847 None => return Ok(None),
848 };
849 let ranges = selection_ranges(&doc, ¶ms.positions);
850 Ok(if ranges.is_empty() {
851 None
852 } else {
853 Some(ranges)
854 })
855 }
856
857 async fn prepare_call_hierarchy(
858 &self,
859 params: CallHierarchyPrepareParams,
860 ) -> Result<Option<Vec<CallHierarchyItem>>> {
861 let uri = ¶ms.text_document_position_params.text_document.uri;
862 let position = params.text_document_position_params.position;
863 let source = self.get_open_text(uri).unwrap_or_default();
864 let word = match word_at_position(&source, position) {
865 Some(w) => w,
866 None => return Ok(None),
867 };
868 let wi = self.workspace_index_async().await;
871 let docs = Arc::clone(&self.docs);
872 let get_doc = move |u: &Url| docs.get_doc_salsa(u);
873 Ok(prepare_call_hierarchy_indexed(&word, &wi, &get_doc).map(|item| vec![item]))
874 }
875
876 async fn incoming_calls(
877 &self,
878 params: CallHierarchyIncomingCallsParams,
879 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
880 let docs = Arc::clone(&self.docs);
883 let item = params.item;
884 let calls = tokio::task::spawn_blocking(move || {
885 let all_docs = docs.all_docs_for_scan();
886 incoming_calls(&item, &all_docs)
887 })
888 .await
889 .unwrap_or_default();
890 Ok(if calls.is_empty() { None } else { Some(calls) })
891 }
892
893 async fn outgoing_calls(
894 &self,
895 params: CallHierarchyOutgoingCallsParams,
896 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
897 let wi = self.workspace_index_async().await;
900 let docs = Arc::clone(&self.docs);
901 let item = params.item;
902 let calls = tokio::task::spawn_blocking(move || {
903 let get_doc = |u: &Url| docs.get_doc_salsa(u);
904 outgoing_calls_indexed(&item, &wi, &get_doc)
905 })
906 .await
907 .unwrap_or_default();
908 Ok(if calls.is_empty() { None } else { Some(calls) })
909 }
910
911 async fn document_highlight(
912 &self,
913 params: DocumentHighlightParams,
914 ) -> Result<Option<Vec<DocumentHighlight>>> {
915 let uri = ¶ms.text_document_position_params.text_document.uri;
916 let position = params.text_document_position_params.position;
917 let source = self.get_open_text(uri).unwrap_or_default();
918 let doc = match self.get_doc(uri) {
919 Some(d) => d,
920 None => return Ok(None),
921 };
922 let highlights = document_highlights(&source, &doc, position);
923 Ok(if highlights.is_empty() {
924 None
925 } else {
926 Some(highlights)
927 })
928 }
929
930 async fn linked_editing_range(
931 &self,
932 params: LinkedEditingRangeParams,
933 ) -> Result<Option<LinkedEditingRanges>> {
934 self.handle_linked_editing_range(params).await
935 }
936
937 async fn goto_implementation(
938 &self,
939 params: tower_lsp::lsp_types::request::GotoImplementationParams,
940 ) -> Result<Option<tower_lsp::lsp_types::request::GotoImplementationResponse>> {
941 let uri = ¶ms.text_document_position_params.text_document.uri;
942 let position = params.text_document_position_params.position;
943 let source = self.get_open_text(uri).unwrap_or_default();
944 let imports = self.file_imports(uri);
945 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
946 let (word, fqn_owned): (String, Option<String>) = if raw_word.contains('\\') {
951 let short = raw_word
952 .rsplit('\\')
953 .next()
954 .unwrap_or(&raw_word)
955 .to_string();
956 let full = raw_word.trim_start_matches('\\').to_string();
957 (short, Some(full))
958 } else {
959 let fqn = imports.get(&raw_word).cloned();
960 (raw_word, fqn)
961 };
962 let fqn = fqn_owned.as_deref();
963 let open_docs = self.docs.docs_for(&self.open_urls());
965 let mut locs = find_implementations(&word, fqn, &open_docs);
966 let wi = self.workspace_index_async().await;
968 if locs.is_empty() {
969 locs = find_implementations_from_workspace(&word, fqn, &wi);
970 }
971 if locs.is_empty()
975 && let Some(doc) = self.get_doc(uri)
976 && let Some(enclosing) =
977 crate::types::type_map::enclosing_class_at(&source, &doc, position)
978 {
979 locs = find_method_implementations_from_workspace(&word, &enclosing, &wi);
980 }
981 if locs.is_empty() {
982 Ok(None)
983 } else {
984 Ok(Some(GotoDefinitionResponse::Array(locs)))
985 }
986 }
987
988 async fn goto_declaration(
989 &self,
990 params: tower_lsp::lsp_types::request::GotoDeclarationParams,
991 ) -> Result<Option<tower_lsp::lsp_types::request::GotoDeclarationResponse>> {
992 let uri = ¶ms.text_document_position_params.text_document.uri;
993 let position = params.text_document_position_params.position;
994 let source = self.get_open_text(uri).unwrap_or_default();
995 let open_docs = self.docs.docs_for(&self.open_urls());
997 if let Some(loc) = goto_declaration(&source, &open_docs, position) {
998 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
999 }
1000 let all_indexes = self.docs.all_indexes();
1002 Ok(goto_declaration_from_index(&source, &all_indexes, position)
1003 .map(GotoDefinitionResponse::Scalar))
1004 }
1005
1006 async fn goto_type_definition(
1007 &self,
1008 params: tower_lsp::lsp_types::request::GotoTypeDefinitionParams,
1009 ) -> Result<Option<tower_lsp::lsp_types::request::GotoTypeDefinitionResponse>> {
1010 let uri = ¶ms.text_document_position_params.text_document.uri;
1011 let position = params.text_document_position_params.position;
1012 let source = self.get_open_text(uri).unwrap_or_default();
1013 let doc = match self.get_doc(uri) {
1014 Some(d) => d,
1015 None => return Ok(None),
1016 };
1017 let analysis = self.cached_analysis_async(uri).await;
1018 let open_docs = self.docs.docs_for(&self.open_urls());
1020 let mut results =
1021 goto_type_definition(&source, &doc, analysis.as_deref(), &open_docs, position);
1022
1023 if results.is_empty() {
1025 let all_indexes = self.docs.all_indexes();
1026 results = goto_type_definition_from_index(
1027 &source,
1028 &doc,
1029 analysis.as_deref(),
1030 &all_indexes,
1031 position,
1032 );
1033 }
1034
1035 let response = match results.len() {
1037 0 => None,
1038 1 => Some(GotoDefinitionResponse::Scalar(
1039 results.into_iter().next().unwrap(),
1040 )),
1041 _ => Some(GotoDefinitionResponse::Array(results)),
1042 };
1043 Ok(response)
1044 }
1045
1046 async fn prepare_type_hierarchy(
1047 &self,
1048 params: TypeHierarchyPrepareParams,
1049 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1050 let uri = ¶ms.text_document_position_params.text_document.uri;
1051 let position = params.text_document_position_params.position;
1052 let source = self.get_open_text(uri).unwrap_or_default();
1053 let wi = self.workspace_index_async().await;
1055 Ok(prepare_type_hierarchy_from_workspace(&source, &wi, position).map(|item| vec![item]))
1056 }
1057
1058 async fn supertypes(
1059 &self,
1060 params: TypeHierarchySupertypesParams,
1061 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1062 let wi = self.workspace_index_async().await;
1066 let loaded_new = self
1067 .ensure_direct_supertypes_loaded(¶ms.item.name, &wi)
1068 .await;
1069 let wi = if loaded_new {
1070 self.workspace_index_async().await
1071 } else {
1072 wi
1073 };
1074 let result = supertypes_of_from_workspace(¶ms.item, &wi);
1075 Ok(if result.is_empty() {
1076 None
1077 } else {
1078 Some(result)
1079 })
1080 }
1081
1082 async fn subtypes(
1083 &self,
1084 params: TypeHierarchySubtypesParams,
1085 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1086 let wi = self.workspace_index_async().await;
1088 let result = subtypes_of_from_workspace(¶ms.item, &wi);
1089 Ok(if result.is_empty() {
1090 None
1091 } else {
1092 Some(result)
1093 })
1094 }
1095
1096 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
1097 let uri = ¶ms.text_document.uri;
1098 let doc = match self.get_doc(uri) {
1099 Some(d) => d,
1100 None => return Ok(None),
1101 };
1102 let docs = Arc::clone(&self.docs);
1105 let uri_owned = uri.clone();
1106 let lenses = tokio::task::spawn_blocking(move || {
1107 let all_docs = docs.all_docs_for_scan();
1108 code_lenses(&uri_owned, &doc, &all_docs)
1109 })
1110 .await
1111 .unwrap_or_default();
1112 Ok(if lenses.is_empty() {
1113 None
1114 } else {
1115 Some(lenses)
1116 })
1117 }
1118
1119 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
1120 Ok(params)
1122 }
1123
1124 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
1125 let uri = ¶ms.text_document.uri;
1126 let doc = match self.get_doc(uri) {
1127 Some(d) => d,
1128 None => return Ok(None),
1129 };
1130 let links = document_links(uri, &doc, doc.source());
1131 Ok(if links.is_empty() { None } else { Some(links) })
1132 }
1133
1134 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
1135 Ok(params)
1137 }
1138
1139 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
1140 let uri = ¶ms.text_document.uri;
1141 let source = self.get_open_text(uri).unwrap_or_default();
1142 Ok(format_document(&source))
1143 }
1144
1145 async fn range_formatting(
1146 &self,
1147 params: DocumentRangeFormattingParams,
1148 ) -> Result<Option<Vec<TextEdit>>> {
1149 let uri = ¶ms.text_document.uri;
1150 let source = self.get_open_text(uri).unwrap_or_default();
1151 Ok(format_range(&source, params.range))
1152 }
1153
1154 async fn on_type_formatting(
1155 &self,
1156 params: DocumentOnTypeFormattingParams,
1157 ) -> Result<Option<Vec<TextEdit>>> {
1158 let uri = ¶ms.text_document_position.text_document.uri;
1159 let source = self.get_open_text(uri).unwrap_or_default();
1160 let edits = on_type_format(
1161 &source,
1162 params.text_document_position.position,
1163 ¶ms.ch,
1164 ¶ms.options,
1165 );
1166 Ok(if edits.is_empty() { None } else { Some(edits) })
1167 }
1168
1169 async fn execute_command(
1170 &self,
1171 params: ExecuteCommandParams,
1172 ) -> Result<Option<serde_json::Value>> {
1173 match params.command.as_str() {
1174 "php-lsp.runTest" => {
1175 let file_uri = params
1177 .arguments
1178 .first()
1179 .and_then(|v| v.as_str())
1180 .and_then(|s| Url::parse(s).ok());
1181 let filter = params
1182 .arguments
1183 .get(1)
1184 .and_then(|v| v.as_str())
1185 .unwrap_or("")
1186 .to_string();
1187
1188 let root = self.root_paths.load().first().cloned();
1189 let client = self.client.clone();
1190
1191 tokio::spawn(async move {
1192 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
1193 });
1194
1195 Ok(None)
1196 }
1197 _ => Ok(None),
1198 }
1199 }
1200
1201 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
1202 self.handle_will_rename_files(params).await
1203 }
1204
1205 async fn did_rename_files(&self, params: RenameFilesParams) {
1206 self.handle_did_rename_files(params).await
1207 }
1208
1209 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
1210 self.handle_will_create_files(params).await
1211 }
1212
1213 async fn did_create_files(&self, params: CreateFilesParams) {
1214 self.handle_did_create_files(params).await
1215 }
1216
1217 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
1220 self.handle_will_delete_files(params).await
1221 }
1222
1223 async fn did_delete_files(&self, params: DeleteFilesParams) {
1224 self.handle_did_delete_files(params).await
1225 }
1226
1227 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
1228 let uri = ¶ms.text_document_position_params.text_document.uri;
1229 let position = params.text_document_position_params.position;
1230 let source = self.get_open_text(uri).unwrap_or_default();
1231 let doc = match self.get_doc(uri) {
1232 Some(d) => d,
1233 None => return Ok(None),
1234 };
1235 let imports = self.file_imports(uri);
1236 Ok(moniker_at(&source, &doc, position, &imports).map(|m| vec![m]))
1237 }
1238
1239 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
1240 let uri = ¶ms.text_document.uri;
1241 let source = self.get_open_text(uri).unwrap_or_default();
1242 let values = inline_values_in_range(&source, params.range);
1243 Ok(if values.is_empty() {
1244 None
1245 } else {
1246 Some(values)
1247 })
1248 }
1249
1250 async fn diagnostic(
1251 &self,
1252 params: DocumentDiagnosticParams,
1253 ) -> Result<DocumentDiagnosticReportResult> {
1254 self.handle_diagnostic(params).await
1255 }
1256
1257 async fn workspace_diagnostic(
1258 &self,
1259 params: WorkspaceDiagnosticParams,
1260 ) -> Result<WorkspaceDiagnosticReportResult> {
1261 self.handle_workspace_diagnostic(params).await
1262 }
1263
1264 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
1265 self.handle_code_action(params).await
1266 }
1267
1268 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
1269 self.handle_code_action_resolve(item).await
1270 }
1271}