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, 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 cfg = self.config.load();
310 let diag_cfg = cfg.diagnostics.clone();
311 let debounce_ms = cfg.debounce_ms;
312 tokio::spawn(async move {
313 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
316
317 if open_files.current_version(&uri) != Some(version) {
321 return;
322 }
323
324 let (_doc, parse_diags) =
325 tokio::task::spawn_blocking(move || parse_document(&text))
326 .await
327 .unwrap_or_else(|_| (ParsedDoc::default(), vec![]));
328
329 if open_files.current_version(&uri) == Some(version) {
331 open_files.set_parse_diagnostics(&uri, parse_diags);
332 publish_with_dependents(client, docs, open_files, uri, diag_cfg).await;
333 }
334 });
335 })
336 .await
337 }
338
339 async fn did_close(&self, params: DidCloseTextDocumentParams) {
340 let uri = params.text_document.uri;
341 self.close_open_file(&uri);
342 self.client.publish_diagnostics(uri, vec![], None).await;
344 }
345
346 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
347
348 async fn will_save_wait_until(
349 &self,
350 params: WillSaveTextDocumentParams,
351 ) -> Result<Option<Vec<TextEdit>>> {
352 let source = self
353 .get_open_text(¶ms.text_document.uri)
354 .unwrap_or_default();
355 Ok(format_document(&source))
356 }
357
358 async fn did_save(&self, params: DidSaveTextDocumentParams) {
359 let uri = params.text_document.uri;
360 let diag_cfg = self.config.load().diagnostics.clone();
366 let all = compute_open_file_diagnostics(&self.docs, &self.open_files, &uri, &diag_cfg);
367 self.client
368 .publish_diagnostics(uri.clone(), all, None)
369 .await;
370
371 if let (Some(root), Ok(path)) =
383 (self.root_paths.load().first().cloned(), uri.to_file_path())
384 {
385 tokio::task::spawn_blocking(move || {
386 let Some(cache) = crate::index::cache::WorkspaceCache::new(&root) else {
387 return;
388 };
389 let Ok(meta) = std::fs::metadata(&path) else {
392 return;
393 };
394 let Ok(text) = std::fs::read_to_string(&path) else {
395 return;
396 };
397 let mtime_secs = meta
398 .modified()
399 .ok()
400 .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok())
401 .map(|d| d.as_secs())
402 .unwrap_or(0);
403 let key = crate::index::cache::WorkspaceCache::key_for_stat(
404 uri.as_str(),
405 mtime_secs,
406 meta.len(),
407 );
408 let doc = parse_document_no_diags(&text);
409 let index = crate::index::file_index::FileIndex::extract(&doc);
410 let _ = cache.write(&key, &index);
411 });
412 }
413 }
414
415 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
416 for change in params.changes {
417 match change.typ {
418 FileChangeType::CREATED | FileChangeType::CHANGED => {
419 if let Ok(path) = change.uri.to_file_path()
420 && let Ok(text) = tokio::fs::read_to_string(&path).await
421 {
422 let doc = parse_document_no_diags(&text);
427 self.ingest_from_doc_if_not_open(change.uri.clone(), &doc);
428 }
429 }
430 FileChangeType::DELETED => {
431 self.docs.remove(&change.uri);
432 }
433 _ => {}
434 }
435 }
436 send_refresh_requests(&self.client).await;
438 }
439
440 #[tracing::instrument(skip_all)]
441 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
442 guard_async_result("completion", async move {
443 let uri = ¶ms.text_document_position.text_document.uri;
444 let position = params.text_document_position.position;
445 let source = self.get_open_text(uri).unwrap_or_default();
446 let doc = match self.get_doc(uri) {
447 Some(d) => d,
448 None => return Ok(Some(CompletionResponse::Array(vec![]))),
449 };
450 let other_docs: Vec<Arc<ParsedDoc>> = self
451 .docs
452 .other_docs(uri, &self.open_urls())
453 .into_iter()
454 .map(|(_, d)| d)
455 .collect();
456 let trigger = params
457 .context
458 .as_ref()
459 .and_then(|c| c.trigger_character.as_deref());
460 let meta_loaded = self.meta.load();
461 let meta_opt = if meta_loaded.is_empty() {
462 None
463 } else {
464 Some(&**meta_loaded)
465 };
466 let imports = self.file_imports(uri);
467 let wi = self.workspace_index_async().await;
468 let docs_for_lookup = Arc::clone(&self.docs);
469 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
470 let cr = *wi.classes_by_name.get(name)?.first()?;
471 let (uri, _) = wi.at(cr)?;
472 docs_for_lookup.get_doc_salsa(uri)
473 };
474 let analysis = self.cached_analysis_async(uri).await;
475 let docs_for_tm = Arc::clone(&self.docs);
479 let doc_for_tm = Arc::clone(&doc);
480 let uri_for_tm = uri.clone();
481 let get_type_map =
482 move || docs_for_tm.cached_type_map(&uri_for_tm, &doc_for_tm, meta_opt);
483 let session = self
484 .docs
485 .analysis_session(self.docs.workspace_php_version());
486 let ctx = CompletionCtx {
487 source: Some(&source),
488 position: Some(position),
489 meta: meta_opt,
490 doc_uri: Some(uri),
491 file_imports: Some(&imports),
492 find_class_doc: Some(&find_class_doc_fn),
493 analysis: analysis.as_deref(),
494 type_map: Some(&get_type_map),
495 session: Some(session),
496 };
497 Ok(Some(CompletionResponse::Array(filtered_completions_at(
498 &doc,
499 &other_docs,
500 trigger,
501 &ctx,
502 ))))
503 })
504 .await
505 }
506
507 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
508 if item.documentation.is_some() && item.detail.is_some() {
509 return Ok(item);
510 }
511 let name = item.label.trim_end_matches(':');
513 let all_indexes = self.docs.all_indexes();
514 if item.detail.is_none()
515 && let Some(sig) = signature_for_symbol_from_index(name, &all_indexes)
516 {
517 item.detail = Some(sig);
518 }
519 if item.documentation.is_none()
520 && let Some(md) = docs_for_symbol_from_index(name, &all_indexes)
521 {
522 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
523 kind: MarkupKind::Markdown,
524 value: md,
525 }));
526 }
527 Ok(item)
528 }
529
530 async fn goto_definition(
531 &self,
532 params: GotoDefinitionParams,
533 ) -> Result<Option<GotoDefinitionResponse>> {
534 self.handle_goto_definition(params).await
535 }
536
537 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
538 self.handle_references(params).await
539 }
540
541 async fn prepare_rename(
542 &self,
543 params: TextDocumentPositionParams,
544 ) -> Result<Option<PrepareRenameResponse>> {
545 let uri = ¶ms.text_document.uri;
546 let source = self.get_open_text(uri).unwrap_or_default();
547 Ok(prepare_rename(&source, params.position).map(PrepareRenameResponse::Range))
548 }
549
550 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
551 let uri = ¶ms.text_document_position.text_document.uri;
552 let position = params.text_document_position.position;
553 let source = self.get_open_text(uri).unwrap_or_default();
554 let word = match word_at_position(&source, position) {
555 Some(w) => w,
556 None => return Ok(None),
557 };
558 if word.starts_with('$') {
559 let doc = match self.get_doc(uri) {
560 Some(d) => d,
561 None => return Ok(None),
562 };
563 let prop_name = cursor_is_on_property_decl(&source, &doc.program().stmts, position)
567 .or_else(|| promoted_property_at_cursor(&source, &doc.program().stmts, position));
568 if let Some(prop_name) = prop_name {
569 let all_docs = self.docs.all_docs_for_scan();
570 return Ok(Some(rename_property(
571 &prop_name,
572 ¶ms.new_name,
573 &all_docs,
574 )));
575 }
576 Ok(Some(rename_variable(
577 &word,
578 ¶ms.new_name,
579 uri,
580 &doc,
581 position,
582 )))
583 } else if is_after_arrow(&source, position) {
584 let all_docs = self.docs.all_docs_for_scan();
585 Ok(Some(rename_property(&word, ¶ms.new_name, &all_docs)))
586 } else {
587 let all_docs = self.docs.all_docs_for_scan();
588 let doc_opt = self.get_doc(uri);
589 let target_fqn: Option<String> = doc_opt.as_ref().map(|doc| {
590 let imports = self.file_imports(uri);
591 crate::navigation::moniker::resolve_fqn(doc, &word, &imports)
592 });
593 Ok(Some(rename(
594 &word,
595 ¶ms.new_name,
596 &all_docs,
597 target_fqn.as_deref(),
598 )))
599 }
600 }
601
602 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
603 let uri = ¶ms.text_document_position_params.text_document.uri;
604 let position = params.text_document_position_params.position;
605 let source = self.get_open_text(uri).unwrap_or_default();
606 let doc = match self.get_doc(uri) {
607 Some(d) => d,
608 None => return Ok(None),
609 };
610 let all_indexes = self.docs.all_indexes();
611 Ok(signature_help(&source, &doc, position, &all_indexes))
612 }
613
614 #[tracing::instrument(skip_all)]
615 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
616 guard_async_result("hover", async move {
617 let uri = ¶ms.text_document_position_params.text_document.uri;
618 let position = params.text_document_position_params.position;
619 let source = self.get_open_text(uri).unwrap_or_default();
620 let doc = match self.get_doc(uri) {
621 Some(d) => d,
622 None => return Ok(None),
623 };
624 let other_docs = self.docs.other_docs(uri, &self.open_urls());
625 let other_maps = self.docs.other_symbol_maps(uri, &self.open_urls());
626 let analysis = self.cached_analysis_async(uri).await;
627 let hover_session = self
628 .docs
629 .analysis_session(self.docs.workspace_php_version());
630 let result = hover_info_with_maps(
631 &source,
632 &doc,
633 analysis.as_deref(),
634 position,
635 &other_docs,
636 &other_maps,
637 Some(&hover_session),
638 );
639 if result.is_some() {
640 return Ok(result);
641 }
642 if let Some(word) = crate::text::word_at_position(&source, position) {
647 let wi = self.workspace_index_async().await;
648 if let Some(h) = class_hover_from_index(&word, &wi.files) {
650 return Ok(Some(h));
651 }
652 if let Some(resolved) = crate::hover::resolve_use_alias(&doc.program().stmts, &word)
654 && let Some(h) = class_hover_from_index(&resolved, &wi.files)
655 {
656 return Ok(Some(h));
657 }
658 if let Some(line_text) = source.lines().nth(position.line as usize)
660 && let Some(class_token) =
661 extract_static_class_before_cursor(line_text, position.character as usize)
662 {
663 if let Some(h) = method_hover_from_index(&class_token, &word, &wi.files) {
664 return Ok(Some(h));
665 }
666 if let Some(resolved_class) =
667 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
668 && let Some(h) = method_hover_from_index(&resolved_class, &word, &wi.files)
669 {
670 return Ok(Some(h));
671 }
672 }
673 }
674 Ok(None)
675 })
676 .await
677 }
678
679 async fn document_symbol(
680 &self,
681 params: DocumentSymbolParams,
682 ) -> Result<Option<DocumentSymbolResponse>> {
683 let uri = ¶ms.text_document.uri;
684 let doc = match self.get_doc(uri) {
685 Some(d) => d,
686 None => return Ok(None),
687 };
688 Ok(Some(DocumentSymbolResponse::Nested(document_symbols(
689 doc.source(),
690 &doc,
691 ))))
692 }
693
694 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
695 let uri = ¶ms.text_document.uri;
696 let doc = match self.get_doc(uri) {
697 Some(d) => d,
698 None => return Ok(None),
699 };
700 let ranges = folding_ranges(doc.source(), &doc);
701 Ok(if ranges.is_empty() {
702 None
703 } else {
704 Some(ranges)
705 })
706 }
707
708 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
709 let uri = ¶ms.text_document.uri;
710 let doc = match self.get_doc(uri) {
711 Some(d) => d,
712 None => return Ok(None),
713 };
714 let analysis = self.cached_analysis_async(uri).await;
715 let wi = self.workspace_index_async().await;
716 Ok(Some(inlay_hints(
717 doc.source(),
718 &doc,
719 analysis.as_deref(),
720 params.range,
721 &wi.files,
722 )))
723 }
724
725 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
726 if item.tooltip.is_some() {
727 return Ok(item);
728 }
729 let func_name = item
730 .data
731 .as_ref()
732 .and_then(|d| d.get("php_lsp_fn"))
733 .and_then(|v| v.as_str())
734 .map(str::to_string);
735 if let Some(name) = func_name {
736 let all_indexes = self.docs.all_indexes();
737 if let Some(md) = docs_for_symbol_from_index(&name, &all_indexes) {
738 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
739 kind: MarkupKind::Markdown,
740 value: md,
741 }));
742 }
743 }
744 Ok(item)
745 }
746
747 async fn symbol(
748 &self,
749 params: WorkspaceSymbolParams,
750 ) -> Result<Option<Vec<SymbolInformation>>> {
751 let wi = self.workspace_index_async().await;
755 let results = workspace_symbols_from_workspace(¶ms.query, &wi);
756 Ok(Some(results))
757 }
758
759 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
760 let docs = self.docs.docs_for(&self.open_urls());
762 Ok(resolve_workspace_symbol(params, &docs))
763 }
764
765 #[tracing::instrument(skip_all)]
766 async fn semantic_tokens_full(
767 &self,
768 params: SemanticTokensParams,
769 ) -> Result<Option<SemanticTokensResult>> {
770 guard_async_result("semantic_tokens_full", async move {
771 let uri = ¶ms.text_document.uri;
772 let doc = match self.get_doc(uri) {
773 Some(d) => d,
774 None => {
775 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
776 result_id: None,
777 data: vec![],
778 })));
779 }
780 };
781 let tokens = semantic_tokens(doc.source(), &doc);
782 let result_id = token_hash(&tokens);
783 let tokens_arc = Arc::new(tokens);
784 self.docs
785 .store_token_cache(uri, result_id.clone(), Arc::clone(&tokens_arc));
786 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
787 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
788 result_id: Some(result_id),
789 data,
790 })))
791 })
792 .await
793 }
794
795 async fn semantic_tokens_range(
796 &self,
797 params: SemanticTokensRangeParams,
798 ) -> Result<Option<SemanticTokensRangeResult>> {
799 let uri = ¶ms.text_document.uri;
800 let doc = match self.get_doc(uri) {
801 Some(d) => d,
802 None => {
803 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
804 result_id: None,
805 data: vec![],
806 })));
807 }
808 };
809 let tokens = semantic_tokens_range(doc.source(), &doc, params.range);
810 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
811 result_id: None,
812 data: tokens,
813 })))
814 }
815
816 async fn semantic_tokens_full_delta(
817 &self,
818 params: SemanticTokensDeltaParams,
819 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
820 let uri = ¶ms.text_document.uri;
821 let doc = match self.get_doc(uri) {
822 Some(d) => d,
823 None => return Ok(None),
824 };
825
826 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
827 let new_result_id = token_hash(&new_tokens);
828 let prev_id = ¶ms.previous_result_id;
829
830 let result = match self.docs.get_token_cache(uri, prev_id) {
831 Some(old_tokens) => {
832 let edits = compute_token_delta(&old_tokens, &new_tokens);
833 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
834 result_id: Some(new_result_id.clone()),
835 edits,
836 })
837 }
838 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
840 result_id: Some(new_result_id.clone()),
841 data: (*new_tokens).clone(),
842 }),
843 };
844
845 self.docs.store_token_cache(uri, new_result_id, new_tokens);
846 Ok(Some(result))
847 }
848
849 async fn selection_range(
850 &self,
851 params: SelectionRangeParams,
852 ) -> Result<Option<Vec<SelectionRange>>> {
853 let uri = ¶ms.text_document.uri;
854 let doc = match self.get_doc(uri) {
855 Some(d) => d,
856 None => return Ok(None),
857 };
858 let ranges = selection_ranges(&doc, ¶ms.positions);
859 Ok(if ranges.is_empty() {
860 None
861 } else {
862 Some(ranges)
863 })
864 }
865
866 async fn prepare_call_hierarchy(
867 &self,
868 params: CallHierarchyPrepareParams,
869 ) -> Result<Option<Vec<CallHierarchyItem>>> {
870 let uri = ¶ms.text_document_position_params.text_document.uri;
871 let position = params.text_document_position_params.position;
872 let source = self.get_open_text(uri).unwrap_or_default();
873 let word = match word_at_position(&source, position) {
874 Some(w) => w,
875 None => return Ok(None),
876 };
877 let wi = self.workspace_index_async().await;
880 let docs = Arc::clone(&self.docs);
881 let get_doc = move |u: &Url| docs.get_doc_salsa(u);
882 Ok(prepare_call_hierarchy_indexed(&word, &wi, &get_doc).map(|item| vec![item]))
883 }
884
885 async fn incoming_calls(
886 &self,
887 params: CallHierarchyIncomingCallsParams,
888 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
889 let docs = Arc::clone(&self.docs);
892 let item = params.item;
893 let calls = tokio::task::spawn_blocking(move || {
894 let all_docs = docs.all_docs_for_scan();
895 incoming_calls(&item, &all_docs)
896 })
897 .await
898 .unwrap_or_default();
899 Ok(if calls.is_empty() { None } else { Some(calls) })
900 }
901
902 async fn outgoing_calls(
903 &self,
904 params: CallHierarchyOutgoingCallsParams,
905 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
906 let wi = self.workspace_index_async().await;
909 let docs = Arc::clone(&self.docs);
910 let item = params.item;
911 let calls = tokio::task::spawn_blocking(move || {
912 let get_doc = |u: &Url| docs.get_doc_salsa(u);
913 outgoing_calls_indexed(&item, &wi, &get_doc)
914 })
915 .await
916 .unwrap_or_default();
917 Ok(if calls.is_empty() { None } else { Some(calls) })
918 }
919
920 async fn document_highlight(
921 &self,
922 params: DocumentHighlightParams,
923 ) -> Result<Option<Vec<DocumentHighlight>>> {
924 let uri = ¶ms.text_document_position_params.text_document.uri;
925 let position = params.text_document_position_params.position;
926 let source = self.get_open_text(uri).unwrap_or_default();
927 let doc = match self.get_doc(uri) {
928 Some(d) => d,
929 None => return Ok(None),
930 };
931 let highlights = document_highlights(&source, &doc, position);
932 Ok(if highlights.is_empty() {
933 None
934 } else {
935 Some(highlights)
936 })
937 }
938
939 async fn linked_editing_range(
940 &self,
941 params: LinkedEditingRangeParams,
942 ) -> Result<Option<LinkedEditingRanges>> {
943 self.handle_linked_editing_range(params).await
944 }
945
946 async fn goto_implementation(
947 &self,
948 params: tower_lsp::lsp_types::request::GotoImplementationParams,
949 ) -> Result<Option<tower_lsp::lsp_types::request::GotoImplementationResponse>> {
950 let uri = ¶ms.text_document_position_params.text_document.uri;
951 let position = params.text_document_position_params.position;
952 let source = self.get_open_text(uri).unwrap_or_default();
953 let imports = self.file_imports(uri);
954 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
955 let (word, fqn_owned): (String, Option<String>) = if raw_word.contains('\\') {
960 let short = raw_word
961 .rsplit('\\')
962 .next()
963 .unwrap_or(&raw_word)
964 .to_string();
965 let full = raw_word.trim_start_matches('\\').to_string();
966 (short, Some(full))
967 } else {
968 let fqn = imports.get(&raw_word).cloned();
969 (raw_word, fqn)
970 };
971 let fqn = fqn_owned.as_deref();
972 let open_docs = self.docs.docs_for(&self.open_urls());
974 let mut locs = find_implementations(&word, fqn, &open_docs);
975 let wi = self.workspace_index_async().await;
977 if locs.is_empty() {
978 locs = find_implementations_from_workspace(&word, fqn, &wi);
979 }
980 if locs.is_empty()
984 && let Some(doc) = self.get_doc(uri)
985 && let Some(enclosing) =
986 crate::types::type_map::enclosing_class_at(&source, &doc, position)
987 {
988 locs = find_method_implementations_from_workspace(&word, &enclosing, &wi);
989 }
990 if locs.is_empty() {
991 Ok(None)
992 } else {
993 Ok(Some(GotoDefinitionResponse::Array(locs)))
994 }
995 }
996
997 async fn goto_declaration(
998 &self,
999 params: tower_lsp::lsp_types::request::GotoDeclarationParams,
1000 ) -> Result<Option<tower_lsp::lsp_types::request::GotoDeclarationResponse>> {
1001 let uri = ¶ms.text_document_position_params.text_document.uri;
1002 let position = params.text_document_position_params.position;
1003 let source = self.get_open_text(uri).unwrap_or_default();
1004 let open_docs = self.docs.docs_for(&self.open_urls());
1006 if let Some(loc) = goto_declaration(&source, &open_docs, position) {
1007 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1008 }
1009 let all_indexes = self.docs.all_indexes();
1011 Ok(goto_declaration_from_index(&source, &all_indexes, position)
1012 .map(GotoDefinitionResponse::Scalar))
1013 }
1014
1015 async fn goto_type_definition(
1016 &self,
1017 params: tower_lsp::lsp_types::request::GotoTypeDefinitionParams,
1018 ) -> Result<Option<tower_lsp::lsp_types::request::GotoTypeDefinitionResponse>> {
1019 let uri = ¶ms.text_document_position_params.text_document.uri;
1020 let position = params.text_document_position_params.position;
1021 let source = self.get_open_text(uri).unwrap_or_default();
1022 let doc = match self.get_doc(uri) {
1023 Some(d) => d,
1024 None => return Ok(None),
1025 };
1026 let analysis = self.cached_analysis_async(uri).await;
1027 let open_docs = self.docs.docs_for(&self.open_urls());
1029 let mut results =
1030 goto_type_definition(&source, &doc, analysis.as_deref(), &open_docs, position);
1031
1032 if results.is_empty() {
1034 let all_indexes = self.docs.all_indexes();
1035 results = goto_type_definition_from_index(
1036 &source,
1037 &doc,
1038 analysis.as_deref(),
1039 &all_indexes,
1040 position,
1041 );
1042 }
1043
1044 let response = match results.len() {
1046 0 => None,
1047 1 => Some(GotoDefinitionResponse::Scalar(
1048 results.into_iter().next().unwrap(),
1049 )),
1050 _ => Some(GotoDefinitionResponse::Array(results)),
1051 };
1052 Ok(response)
1053 }
1054
1055 async fn prepare_type_hierarchy(
1056 &self,
1057 params: TypeHierarchyPrepareParams,
1058 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1059 let uri = ¶ms.text_document_position_params.text_document.uri;
1060 let position = params.text_document_position_params.position;
1061 let source = self.get_open_text(uri).unwrap_or_default();
1062 let wi = self.workspace_index_async().await;
1064 Ok(prepare_type_hierarchy_from_workspace(&source, &wi, position).map(|item| vec![item]))
1065 }
1066
1067 async fn supertypes(
1068 &self,
1069 params: TypeHierarchySupertypesParams,
1070 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1071 let wi = self.workspace_index_async().await;
1075 let loaded_new = self
1076 .ensure_direct_supertypes_loaded(¶ms.item.name, &wi)
1077 .await;
1078 let wi = if loaded_new {
1079 self.workspace_index_async().await
1080 } else {
1081 wi
1082 };
1083 let result = supertypes_of_from_workspace(¶ms.item, &wi);
1084 Ok(if result.is_empty() {
1085 None
1086 } else {
1087 Some(result)
1088 })
1089 }
1090
1091 async fn subtypes(
1092 &self,
1093 params: TypeHierarchySubtypesParams,
1094 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1095 let wi = self.workspace_index_async().await;
1097 let result = subtypes_of_from_workspace(¶ms.item, &wi);
1098 Ok(if result.is_empty() {
1099 None
1100 } else {
1101 Some(result)
1102 })
1103 }
1104
1105 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
1106 let uri = ¶ms.text_document.uri;
1107 let doc = match self.get_doc(uri) {
1108 Some(d) => d,
1109 None => return Ok(None),
1110 };
1111 let docs = Arc::clone(&self.docs);
1114 let uri_owned = uri.clone();
1115 let lenses = tokio::task::spawn_blocking(move || {
1116 let all_docs = docs.all_docs_for_scan();
1117 code_lenses(&uri_owned, &doc, &all_docs)
1118 })
1119 .await
1120 .unwrap_or_default();
1121 Ok(if lenses.is_empty() {
1122 None
1123 } else {
1124 Some(lenses)
1125 })
1126 }
1127
1128 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
1129 Ok(params)
1131 }
1132
1133 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
1134 let uri = ¶ms.text_document.uri;
1135 let doc = match self.get_doc(uri) {
1136 Some(d) => d,
1137 None => return Ok(None),
1138 };
1139 let links = document_links(uri, &doc, doc.source());
1140 Ok(if links.is_empty() { None } else { Some(links) })
1141 }
1142
1143 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
1144 Ok(params)
1146 }
1147
1148 async fn formatting(&self, params: DocumentFormattingParams) -> 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_document(&source))
1152 }
1153
1154 async fn range_formatting(
1155 &self,
1156 params: DocumentRangeFormattingParams,
1157 ) -> Result<Option<Vec<TextEdit>>> {
1158 let uri = ¶ms.text_document.uri;
1159 let source = self.get_open_text(uri).unwrap_or_default();
1160 Ok(format_range(&source, params.range))
1161 }
1162
1163 async fn on_type_formatting(
1164 &self,
1165 params: DocumentOnTypeFormattingParams,
1166 ) -> Result<Option<Vec<TextEdit>>> {
1167 let uri = ¶ms.text_document_position.text_document.uri;
1168 let source = self.get_open_text(uri).unwrap_or_default();
1169 let edits = on_type_format(
1170 &source,
1171 params.text_document_position.position,
1172 ¶ms.ch,
1173 ¶ms.options,
1174 );
1175 Ok(if edits.is_empty() { None } else { Some(edits) })
1176 }
1177
1178 async fn execute_command(
1179 &self,
1180 params: ExecuteCommandParams,
1181 ) -> Result<Option<serde_json::Value>> {
1182 match params.command.as_str() {
1183 "php-lsp.runTest" => {
1184 let file_uri = params
1186 .arguments
1187 .first()
1188 .and_then(|v| v.as_str())
1189 .and_then(|s| Url::parse(s).ok());
1190 let filter = params
1191 .arguments
1192 .get(1)
1193 .and_then(|v| v.as_str())
1194 .unwrap_or("")
1195 .to_string();
1196
1197 let root = self.root_paths.load().first().cloned();
1198 let client = self.client.clone();
1199
1200 tokio::spawn(async move {
1201 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
1202 });
1203
1204 Ok(None)
1205 }
1206 _ => Ok(None),
1207 }
1208 }
1209
1210 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
1211 self.handle_will_rename_files(params).await
1212 }
1213
1214 async fn did_rename_files(&self, params: RenameFilesParams) {
1215 self.handle_did_rename_files(params).await
1216 }
1217
1218 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
1219 self.handle_will_create_files(params).await
1220 }
1221
1222 async fn did_create_files(&self, params: CreateFilesParams) {
1223 self.handle_did_create_files(params).await
1224 }
1225
1226 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
1229 self.handle_will_delete_files(params).await
1230 }
1231
1232 async fn did_delete_files(&self, params: DeleteFilesParams) {
1233 self.handle_did_delete_files(params).await
1234 }
1235
1236 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
1237 let uri = ¶ms.text_document_position_params.text_document.uri;
1238 let position = params.text_document_position_params.position;
1239 let source = self.get_open_text(uri).unwrap_or_default();
1240 let doc = match self.get_doc(uri) {
1241 Some(d) => d,
1242 None => return Ok(None),
1243 };
1244 let imports = self.file_imports(uri);
1245 Ok(moniker_at(&source, &doc, position, &imports).map(|m| vec![m]))
1246 }
1247
1248 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
1249 let uri = ¶ms.text_document.uri;
1250 let source = self.get_open_text(uri).unwrap_or_default();
1251 let values = inline_values_in_range(&source, params.range);
1252 Ok(if values.is_empty() {
1253 None
1254 } else {
1255 Some(values)
1256 })
1257 }
1258
1259 async fn diagnostic(
1260 &self,
1261 params: DocumentDiagnosticParams,
1262 ) -> Result<DocumentDiagnosticReportResult> {
1263 self.handle_diagnostic(params).await
1264 }
1265
1266 async fn workspace_diagnostic(
1267 &self,
1268 params: WorkspaceDiagnosticParams,
1269 ) -> Result<WorkspaceDiagnosticReportResult> {
1270 self.handle_workspace_diagnostic(params).await
1271 }
1272
1273 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
1274 self.handle_code_action(params).await
1275 }
1276
1277 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
1278 self.handle_code_action_resolve(item).await
1279 }
1280}