1use std::sync::Arc;
2
3use tower_lsp_server::LanguageServer;
4use tower_lsp_server::jsonrpc::Result;
5use tower_lsp_server::ls_types::*;
6
7use super::panic_guard::{guard_async, guard_async_result};
8use crate::analysis::callable_info::{callable_info_for_name, format_declared_params};
9use crate::completion::{CompletionCtx, filtered_completions_at};
10use crate::document::ast::ParsedDoc;
11use crate::document::open_files::compute_open_file_diagnostics;
12use crate::hover::{extract_static_class_before_cursor, hover_info_with_maps};
13use crate::index::file_index::ClassKind;
14use crate::index::workspace_scan::{scan_workspace, send_refresh_requests};
15use crate::lang::config::LspConfig;
16use crate::lang::is_unresolvable_bareword_at;
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_indexed, outgoing_calls_indexed, prepare_call_hierarchy_indexed,
24};
25use crate::navigation::declaration::{goto_declaration, goto_declaration_from_index};
26use crate::navigation::moniker::moniker_at;
27use crate::navigation::type_definition::{
28 goto_type_definition_exact, goto_type_definition_from_index_exact,
29 goto_type_definition_from_index_short_name_fallback, goto_type_definition_short_name_fallback,
30};
31use crate::navigation::type_hierarchy::{
32 prepare_type_hierarchy_from_workspace, subtypes_of_mir_backed, supertypes_of_from_workspace,
33};
34
35use crate::analysis::code_lens::code_lenses;
36use crate::analysis::diagnostics::{diagnostics_from_doc, parse_document, parse_document_no_diags};
37use crate::analysis::document_highlight::document_highlights;
38use crate::analysis::inlay_hints::inlay_hints;
39use crate::analysis::inline_value::inline_values_in_range;
40use crate::analysis::semantic_tokens::{
41 compute_token_delta, semantic_tokens, semantic_tokens_range, token_hash,
42};
43
44use crate::editing::document_link::document_links;
45use crate::editing::folding::folding_ranges;
46use crate::editing::formatting::{format_document, format_range};
47use crate::editing::on_type_format::on_type_format;
48use crate::editing::rename::{prepare_rename, rename_variable};
49use crate::editing::selection_range::selection_ranges;
50use crate::editing::signature_help::signature_help;
51
52use super::helpers::{
53 cursor_is_on_method_decl, cursor_is_on_property_decl, promoted_property_at_cursor, run_phpunit,
54};
55use super::{Backend, publish_with_dependents};
56
57const WARM_RESWEEP_IDLE_MS: u64 = 1_000;
61
62fn inlay_hint_symbol_from_data(data: &serde_json::Value) -> Option<mir_analyzer::Name> {
63 let kind = data.get("php_lsp_symbol_kind")?.as_str()?;
64 let name = data.get("php_lsp_fn")?.as_str()?;
65 match kind {
66 "function" => Some(mir_analyzer::Name::function(name)),
67 "method" => {
68 let class = data.get("php_lsp_class")?.as_str()?;
69 Some(mir_analyzer::Name::method(class, name))
70 }
71 "class" => Some(mir_analyzer::Name::class(name)),
72 _ => None,
73 }
74}
75
76fn completion_symbol_from_item(
77 name: &str,
78 kind: Option<CompletionItemKind>,
79 class_hint: Option<&str>,
80) -> Option<mir_analyzer::Name> {
81 if let Some(class_name) = class_hint {
82 return Some(mir_analyzer::Name::method(class_name, name));
83 }
84 match kind {
85 Some(CompletionItemKind::FUNCTION) => Some(mir_analyzer::Name::function(name)),
86 Some(CompletionItemKind::CLASS)
87 | Some(CompletionItemKind::INTERFACE)
88 | Some(CompletionItemKind::ENUM) => Some(mir_analyzer::Name::class(name)),
89 _ => None,
90 }
91}
92
93fn hover_markup(value: String) -> Hover {
94 Hover {
95 contents: HoverContents::Markup(MarkupContent {
96 kind: MarkupKind::Markdown,
97 value,
98 }),
99 range: None,
100 }
101}
102
103fn hover_display_class_name(session: &mir_analyzer::AnalysisSession, name: &str) -> String {
104 let normalized = name.trim_start_matches('\\');
105 if normalized.contains('\\') {
106 return normalized.to_string();
107 }
108 if crate::types::stub_members::stub_class_members(session, normalized).is_some() {
109 return format!("\\{normalized}");
110 }
111 normalized.to_string()
112}
113
114fn class_hover_for_fqcn(session: &mir_analyzer::AnalysisSession, fqcn: &str) -> Option<Hover> {
115 let db = session.snapshot_db();
116 let here = mir_analyzer::db::Fqcn::from_str(&db, fqcn);
117 let class = mir_analyzer::db::find_class_like(&db, here)?;
118 let sig = match &class {
119 mir_analyzer::db::ClassLike::Class(c) => {
120 let mut sig = if c.is_abstract {
121 format!("abstract class {}", c.short_name)
122 } else if c.is_readonly {
123 format!("readonly class {}", c.short_name)
124 } else if c.is_final {
125 format!("final class {}", c.short_name)
126 } else {
127 format!("class {}", c.short_name)
128 };
129 if let Some(parent) = &c.parent {
130 sig.push_str(&format!(
131 " extends {}",
132 hover_display_class_name(session, parent)
133 ));
134 }
135 if !c.interfaces.is_empty() {
136 let list: Vec<String> = c
137 .interfaces
138 .iter()
139 .map(|s| hover_display_class_name(session, s))
140 .collect();
141 sig.push_str(&format!(" implements {}", list.join(", ")));
142 }
143 sig
144 }
145 mir_analyzer::db::ClassLike::Interface(i) => {
146 let mut sig = format!("interface {}", i.short_name);
147 if !i.extends.is_empty() {
148 let list: Vec<String> = i
149 .extends
150 .iter()
151 .map(|s| hover_display_class_name(session, s))
152 .collect();
153 sig.push_str(&format!(" extends {}", list.join(", ")));
154 }
155 sig
156 }
157 mir_analyzer::db::ClassLike::Trait(t) => format!("trait {}", t.short_name),
158 mir_analyzer::db::ClassLike::Enum(e) => {
159 let mut sig = if let Some(scalar) = &e.scalar_type {
160 format!("enum {}: {}", e.short_name, scalar)
161 } else {
162 format!("enum {}", e.short_name)
163 };
164 if !e.interfaces.is_empty() {
165 let list: Vec<String> = e
166 .interfaces
167 .iter()
168 .map(|s| hover_display_class_name(session, s))
169 .collect();
170 sig.push_str(&format!(" implements {}", list.join(", ")));
171 }
172 sig
173 }
174 };
175 Some(hover_markup(crate::hover::formatting::wrap_php(&sig)))
176}
177
178fn method_hover_for_fqcn(
179 session: &mir_analyzer::AnalysisSession,
180 fqcn: &str,
181 method_name: &str,
182) -> Option<Hover> {
183 let db = session.snapshot_db();
184 let (_, m) = mir_analyzer::db::find_method_in_chain(
185 &db,
186 mir_analyzer::db::Fqcn::from_str(&db, fqcn),
187 method_name,
188 )?;
189 let ret = m
190 .return_type
191 .as_deref()
192 .or(m.inferred_return_type.as_deref())
193 .map(|r| format!(": {}", r))
194 .unwrap_or_default();
195 let sig = format!(
196 "{}::{}({}){}",
197 crate::text::fqn_short_name(fqcn),
198 m.name,
199 format_declared_params(&m.params),
200 ret
201 );
202 let mut value = crate::hover::formatting::wrap_php(&sig);
203 if let Some(docstring) = m.docstring.as_deref().filter(|s| !s.is_empty()) {
204 value.push_str("\n\n---\n\n");
205 value.push_str(docstring);
206 }
207 Some(hover_markup(value))
208}
209
210fn callable_signature_for_symbol(
211 session: &mir_analyzer::AnalysisSession,
212 symbol: &mir_analyzer::Name,
213) -> Option<(String, Option<String>)> {
214 let info = callable_info_for_name(session, symbol)?;
215 match symbol {
216 mir_analyzer::Name::Function(fqn) => {
217 let short = fqn.rsplit('\\').next().unwrap_or(fqn.as_ref());
218 if mir_analyzer::is_builtin_function(short) {
219 return None;
220 }
221 let ret = info
222 .return_type
223 .as_deref()
224 .map(|ty| format!(": {ty}"))
225 .unwrap_or_default();
226 Some((
227 format!("function {}({}){}", short, info.params, ret),
228 info.documentation,
229 ))
230 }
231 mir_analyzer::Name::Method { class: _, name } => {
232 let ret = info
233 .return_type
234 .as_deref()
235 .map(|ty| format!(": {ty}"))
236 .unwrap_or_default();
237 Some((
238 format!("function {}({}){}", name, info.params, ret),
239 info.documentation,
240 ))
241 }
242 mir_analyzer::Name::Class(_) => Some((
243 format!("function __construct({})", info.params),
244 info.documentation,
245 )),
246 _ => None,
247 }
248}
249
250fn inlay_hint_tooltip_for_symbol(
251 session: &mir_analyzer::AnalysisSession,
252 symbol: &mir_analyzer::Name,
253) -> Option<String> {
254 let (signature, docstring) = callable_signature_for_symbol(session, symbol)?;
255 let mut value = format!("```php\n{signature}\n```");
256 if let Some(docstring) = docstring.filter(|s| !s.is_empty()) {
257 value.push_str("\n\n---\n\n");
258 value.push_str(&docstring);
259 }
260 Some(value)
261}
262
263fn completion_signature_and_docstring_for_symbol(
264 session: &mir_analyzer::AnalysisSession,
265 symbol: &mir_analyzer::Name,
266) -> (Option<String>, Option<String>) {
267 if let Some((signature, docstring)) = callable_signature_for_symbol(session, symbol) {
268 return (Some(signature), docstring.filter(|s| !s.is_empty()));
269 }
270 if let mir_analyzer::Name::Function(fqn) = symbol {
271 let short = fqn.rsplit('\\').next().unwrap_or(fqn.as_ref());
272 if mir_analyzer::is_builtin_function(short) {
273 return (
274 None,
275 Some(format!(
276 "```php\nfunction {}()\n```\n\n[php.net documentation]({})",
277 short,
278 crate::lang::php_names::php_doc_url(short)
279 )),
280 );
281 }
282 }
283 (None, None)
284}
285
286impl LanguageServer for Backend {
287 async fn initialize(&self, params: InitializeParams) -> Result<InitializeResult> {
288 self.handle_initialize(params).await
289 }
290
291 async fn initialized(&self, _params: InitializedParams) {
292 guard_async("initialized", async move {
293 self.handle_initialized(_params).await
294 })
295 .await
296 }
297
298 async fn did_change_configuration(&self, _params: DidChangeConfigurationParams) {
299 guard_async("did_change_configuration", async move {
300 let items = vec![ConfigurationItem {
303 scope_uri: None,
304 section: Some("php-lsp".to_string()),
305 }];
306 if let Ok(values) = self.client.configuration(items).await
307 && let Some(value) = values.into_iter().next()
308 {
309 let roots = self.root_paths.load_full();
310
311 let file_cfg = crate::lang::autoload::load_project_config_json(&roots);
314
315 if let Some(ver) = value.get("phpVersion").and_then(|v| v.as_str())
316 && !crate::lang::autoload::is_valid_php_version(ver)
317 {
318 self.client
319 .log_message(
320 tower_lsp_server::ls_types::MessageType::WARNING,
321 format!(
322 "php-lsp: unsupported phpVersion {ver:?} — valid values: {}",
323 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
324 ),
325 )
326 .await;
327 }
328
329 let file_obj = file_cfg.as_ref().filter(|v| v.is_object());
330 let merged = LspConfig::merge_project_configs(file_obj, Some(&value));
331 let mut cfg = LspConfig::from_value(&merged);
332
333 let (ver, source) = self.resolve_php_version(cfg.php_version.as_deref());
335 self.client
336 .log_message(
337 tower_lsp_server::ls_types::MessageType::INFO,
338 format!("php-lsp: using PHP {ver} ({source})"),
339 )
340 .await;
341 let ver = if source != "set by editor"
343 && !crate::lang::autoload::is_valid_php_version(&ver)
344 {
345 let clamped = crate::lang::autoload::clamp_php_version(&ver);
346 self.client
347 .show_message(
348 tower_lsp_server::ls_types::MessageType::WARNING,
349 format!(
350 "php-lsp: detected PHP {ver} is outside the supported range ({}); \
351 using PHP {clamped} for analysis",
352 crate::lang::autoload::SUPPORTED_PHP_VERSIONS.join(", ")
353 ),
354 )
355 .await;
356 clamped.to_string()
357 } else {
358 ver
359 };
360 cfg.php_version = Some(ver.clone());
361 let version_changed = ver
362 .parse::<mir_analyzer::PhpVersion>()
363 .is_ok_and(|pv| self.docs.set_php_version(pv));
364 self.config.store(Arc::new(cfg));
365 if version_changed {
366 self.remirror_open_files();
374 self.rescan_roots_after_version_change(&roots).await;
375 }
376 send_refresh_requests(&self.client).await;
377 }
378 })
379 .await
380 }
381
382 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
383 guard_async("did_change_workspace_folders", async move {
384 {
386 let mut roots = (**self.root_paths.load()).clone();
387 for removed in ¶ms.event.removed {
388 if let Some(path) = removed.uri.to_file_path() {
389 roots.retain(|r| r.as_path() != path.as_ref());
390 }
391 }
392 self.root_paths.store(Arc::new(roots));
393 }
394
395 let (exclude_paths, include_paths, max_indexed_files, cache_path) = {
397 let cfg = self.config.load();
398 let mut exclude = cfg.exclude_paths.clone();
399 if !cfg.index_vendor && !exclude.iter().any(|p| p == "vendor" || p == "vendor/") {
400 exclude.push("vendor/".to_string());
401 }
402 (
403 exclude,
404 cfg.include_paths.clone(),
405 cfg.max_indexed_files,
406 cfg.cache_path.clone(),
407 )
408 };
409 for added in ¶ms.event.added {
410 if let Some(path) = added.uri.to_file_path().map(|c| c.into_owned()) {
411 let is_new = {
412 let mut roots = (**self.root_paths.load()).clone();
413 if !roots.contains(&path) {
414 roots.push(path.clone());
415 self.root_paths.store(Arc::new(roots));
416 true
417 } else {
418 false
419 }
420 };
421 if is_new {
422 let docs = Arc::clone(&self.docs);
423 let open_files = self.open_files.clone();
424 let ex = exclude_paths.clone();
425 let ip = include_paths.clone();
426 let path_clone = path.clone();
427 let client = self.client.clone();
428 let cp = cache_path.clone();
429 tokio::spawn(async move {
430 let cache = if let Some(p) = cp {
431 Some(crate::index::cache::WorkspaceCache::with_dir(p))
432 } else {
433 crate::index::cache::WorkspaceCache::new(&path_clone)
434 };
435 scan_workspace(
436 path_clone,
437 Arc::clone(&docs),
438 open_files,
439 cache,
440 &ex,
441 &ip,
442 max_indexed_files,
443 None,
444 )
445 .await;
446 super::offload::run("workspaceFolders.warmStart", move || {
451 docs.get_workspace_index_salsa();
452 docs.warm_start_indexes();
453 })
454 .await;
455 send_refresh_requests(&client).await;
456 });
457 }
458 }
459 }
460 })
461 .await
462 }
463
464 async fn shutdown(&self) -> Result<()> {
465 let docs = Arc::clone(&self.docs);
468 self.blocking("shutdown.flush", move || docs.flush_analysis_cache())
469 .await;
470 Ok(())
471 }
472
473 #[tracing::instrument(skip_all)]
474 async fn did_open(&self, params: DidOpenTextDocumentParams) {
475 guard_async("did_open", async move {
476 let uri = params.text_document.uri;
477 let text = params.text_document.text;
478
479 self.set_open_text(uri.clone(), text);
483 self.docs.note_new_file_declarations(&uri);
490
491 let docs = Arc::clone(&self.docs);
495 let uri_for_task = uri.clone();
496 let parse_diags = self
497 .blocking_gated(super::debug_gate::GATE_DID_OPEN_PARSE, move || {
498 docs.get_doc_salsa(&uri_for_task)
499 .map(|doc| diagnostics_from_doc(&doc))
500 .unwrap_or_default()
501 })
502 .await
503 .unwrap_or_default();
504 self.set_parse_diagnostics(&uri, parse_diags);
505
506 publish_with_dependents(
507 self.client.clone(),
508 Arc::clone(&self.docs),
509 self.open_files.clone(),
510 uri,
511 self.config.load().diagnostics.clone(),
512 self.laravel.load_full().is_laravel,
513 )
514 .await;
515 })
516 .await
517 }
518
519 #[tracing::instrument(skip_all)]
520 async fn did_change(&self, params: DidChangeTextDocumentParams) {
521 guard_async("did_change", async move {
522 let uri = params.text_document.uri;
523 let mut updated: Option<String> = None;
528 for change in params.content_changes {
529 match change.range {
530 None => updated = Some(change.text),
531 Some(range) => {
532 let mut cur = match updated.take() {
533 Some(t) => t,
534 None => self.get_open_text(&uri).unwrap_or_default().to_string(),
535 };
536 crate::text::apply_content_change(&mut cur, range, &change.text);
537 updated = Some(cur);
538 }
539 }
540 }
541 let Some(text) = updated else { return };
542
543 let version = self.set_open_text(uri.clone(), text.clone());
547
548 let docs = Arc::clone(&self.docs);
549 let open_files = self.open_files.clone();
550 let client = self.client.clone();
551 let cfg = self.config.load();
552 let diag_cfg = cfg.diagnostics.clone();
553 let debounce_ms = cfg.debounce_ms;
554 let warm_analysis = cfg.warm_analysis;
555 let is_laravel = self.laravel.load_full().is_laravel;
556 tokio::spawn(async move {
557 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
560
561 if open_files.current_version(&uri) != Some(version) {
565 return;
566 }
567
568 let (_doc, parse_diags) =
569 super::offload::run("did_change.parse", move || parse_document(&text))
570 .await
571 .unwrap_or_default();
572
573 if open_files.current_version(&uri) != Some(version) {
575 return;
576 }
577 open_files.set_parse_diagnostics(&uri, parse_diags);
578 publish_with_dependents(
579 client,
580 Arc::clone(&docs),
581 open_files.clone(),
582 uri.clone(),
583 diag_cfg,
584 is_laravel,
585 )
586 .await;
587
588 if warm_analysis && docs.is_index_ready() {
593 tokio::time::sleep(std::time::Duration::from_millis(WARM_RESWEEP_IDLE_MS))
594 .await;
595 if open_files.current_version(&uri) != Some(version) {
596 return;
597 }
598 let open_urls = open_files.urls();
599 drop(tokio::task::spawn_blocking(move || {
604 let cancel = docs.begin_warm_sweep();
605 docs.warm_analysis_sweep(&open_urls, &cancel);
606 }));
607 }
608 });
609 })
610 .await
611 }
612
613 async fn did_close(&self, params: DidCloseTextDocumentParams) {
614 guard_async("did_close", async move {
615 let uri = params.text_document.uri;
616 self.close_open_file(&uri);
617 let disk_text = match uri.to_file_path() {
622 Some(path) => tokio::fs::read_to_string(&path).await.ok(),
623 None => None,
624 };
625 if !self.open_files.contains(&uri) {
629 match disk_text {
630 Some(text) => self.docs.ingest(uri.clone(), &text),
631 None => self.docs.remove(&uri),
632 }
633 }
634 self.client.publish_diagnostics(uri, vec![], None).await;
636 })
637 .await
638 }
639
640 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
641
642 async fn will_save_wait_until(
643 &self,
644 params: WillSaveTextDocumentParams,
645 ) -> Result<Option<Vec<TextEdit>>> {
646 guard_async_result("will_save_wait_until", async move {
647 let source = self
648 .get_open_text(¶ms.text_document.uri)
649 .unwrap_or_default();
650 Ok(self
653 .blocking("willSaveWaitUntil.format", move || format_document(&source))
654 .await
655 .unwrap_or_default())
656 })
657 .await
658 }
659
660 async fn did_save(&self, params: DidSaveTextDocumentParams) {
661 guard_async("did_save", async move {
662 let uri = params.text_document.uri;
663 let diag_cfg = self.config.load().diagnostics.clone();
669 let is_laravel = self.laravel.load_full().is_laravel;
670 let docs = Arc::clone(&self.docs);
674 let open_files = self.open_files.clone();
675 let uri_for_diags = uri.clone();
676 let all = self
677 .blocking_gated(super::debug_gate::GATE_DID_SAVE_DIAGNOSTICS, move || {
678 compute_open_file_diagnostics(
679 &docs,
680 &open_files,
681 &uri_for_diags,
682 &diag_cfg,
683 is_laravel,
684 )
685 })
686 .await
687 .unwrap_or_default();
688 self.open_files
689 .note_published(&uri, super::diagnostics_content_hash(&all));
690 self.client
691 .publish_diagnostics(uri.clone(), all, None)
692 .await;
693
694 let cache_path = self.config.load().cache_path.clone();
699 if let Some(path) = uri.to_file_path().map(|c| c.into_owned()) {
700 let root = self.root_paths.load().first().cloned();
701 tokio::task::spawn_blocking({
705 let path = path.clone();
706 let uri = uri.clone();
707 let root = root.clone();
708 move || {
709 let cache = if let Some(p) = cache_path {
710 crate::index::cache::WorkspaceCache::with_dir(p)
711 } else if let Some(r) = &root {
712 let Some(c) = crate::index::cache::WorkspaceCache::new(r) else {
713 return;
714 };
715 c
716 } else {
717 return;
718 };
719 let Ok(text) = std::fs::read_to_string(&path) else {
720 return;
721 };
722 let key = crate::index::cache::WorkspaceCache::key_for(uri.as_str(), &text);
723 let doc = parse_document_no_diags(&text);
724 let index = crate::index::file_index::FileIndex::extract(&doc);
725 let _ = cache.write(&key, &index);
726 }
727 });
728
729 let ext_cfg = self.config.load().external_tools.clone();
737 if (ext_cfg.phpstan.enabled || ext_cfg.phpcs.enabled)
738 && let Some(version) = self.open_files.current_version(&uri)
739 {
740 let client = self.client.clone();
741 let docs = Arc::clone(&self.docs);
742 let open_files = self.open_files.clone();
743 let diag_cfg = self.config.load().diagnostics.clone();
744 let uri = uri.clone();
745 tokio::spawn(async move {
746 let diagnostics = crate::analysis::external::run_external_diagnostics(
747 &ext_cfg,
748 &path,
749 root.as_deref(),
750 )
751 .await;
752 if open_files.current_version(&uri) != Some(version) {
753 return;
754 }
755 open_files.set_external_diagnostics(&uri, version, diagnostics);
756 publish_with_dependents(
757 client, docs, open_files, uri, diag_cfg, is_laravel,
758 )
759 .await;
760 });
761 }
762 }
763 })
764 .await
765 }
766
767 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
768 guard_async("did_change_watched_files", async move {
769 enum Change {
770 Upsert(Uri, String),
771 Delete(Uri),
772 }
773 let mut changes = Vec::with_capacity(params.changes.len());
774 for change in params.changes {
775 match change.typ {
776 FileChangeType::CREATED | FileChangeType::CHANGED => {
777 if let Some(path) = change.uri.to_file_path()
778 && let Ok(text) = tokio::fs::read_to_string(&path).await
779 {
780 changes.push(Change::Upsert(change.uri.clone(), text));
781 }
782 }
783 FileChangeType::DELETED => changes.push(Change::Delete(change.uri.clone())),
784 _ => {}
785 }
786 }
787
788 let docs = Arc::clone(&self.docs);
793 let open_files = self.open_files.clone();
794 self.blocking_gated(
795 super::debug_gate::GATE_DID_CHANGE_WATCHED_FILES,
796 move || {
797 for change in changes {
798 match change {
799 Change::Upsert(uri, text) => {
800 let doc = parse_document_no_diags(&text);
805 if !open_files.contains(&uri) {
806 docs.ingest_from_doc(uri.clone(), &doc);
807 }
808 docs.note_new_file_declarations(&uri);
812 }
813 Change::Delete(uri) => docs.remove(&uri),
814 }
815 }
816 },
817 )
818 .await;
819 send_refresh_requests(&self.client).await;
821
822 if self.config.load().warm_analysis && self.docs.is_index_ready() {
826 let docs = Arc::clone(&self.docs);
827 let open_urls = self.open_files.urls();
828 drop(tokio::task::spawn_blocking(move || {
831 let cancel = docs.begin_warm_sweep();
832 docs.warm_analysis_sweep(&open_urls, &cancel);
833 }));
834 }
835 })
836 .await
837 }
838
839 #[tracing::instrument(skip_all)]
840 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
841 guard_async_result("completion", async move {
842 let uri = ¶ms.text_document_position.text_document.uri;
843 let position = params.text_document_position.position;
844 let cancel_rev = self.docs.write_rev();
845 let source = self.get_open_text(uri).unwrap_or_default();
846 let doc = match self.get_doc(uri) {
847 Some(d) => d,
848 None => return Ok(Some(CompletionResponse::Array(vec![]))),
849 };
850 let other_docs: Vec<Arc<ParsedDoc>> = self
851 .docs
852 .other_docs(uri, &self.open_urls())
853 .into_iter()
854 .map(|(_, d)| d)
855 .collect();
856 let trigger = params
858 .context
859 .as_ref()
860 .and_then(|c| c.trigger_character.clone());
861 let laravel_arc = self.laravel.load_full();
862 let imports = self.file_imports(uri);
863 let wi = self.workspace_index_async().await;
864 let wi_for_class_search = Arc::clone(&wi);
865 let uri_for_class_search = uri.clone();
866 let docs_for_lookup = Arc::clone(&self.docs);
867 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
868 let cr = if name.trim_start_matches('\\').contains('\\') {
869 docs_for_lookup.resolve_class_ref_by_fqn_or_short_name_fallback(&wi, name)?
870 } else {
871 docs_for_lookup.resolve_class_ref_by_short_name(&wi, name)?
872 };
873 let (uri, _) = wi.at(cr)?;
874 docs_for_lookup.get_doc_salsa(uri)
875 };
876 const WORKSPACE_CLASS_SEARCH_LIMIT: usize = 50;
887 let workspace_class_search_fn =
888 move |prefix: &str| -> Vec<(String, CompletionItemKind, String)> {
889 let prefix_lc = prefix.to_lowercase();
890 let table = &wi_for_class_search.classes_by_lowercase_name;
891 let start =
892 table.partition_point(|(name, _)| name.as_ref() < prefix_lc.as_str());
893 let mut out = Vec::new();
894 for (name, cr) in &table[start..] {
895 if !name.starts_with(prefix_lc.as_str()) {
896 break;
897 }
898 let Some((file_uri, cls)) = wi_for_class_search.at(*cr) else {
899 continue;
900 };
901 if *file_uri == uri_for_class_search {
902 continue;
903 }
904 let kind = match cls.kind {
905 ClassKind::Class | ClassKind::Trait => CompletionItemKind::CLASS,
906 ClassKind::Interface => CompletionItemKind::INTERFACE,
907 ClassKind::Enum => CompletionItemKind::ENUM,
908 };
909 out.push((cls.name.to_string(), kind, cls.fqn.to_string()));
910 if out.len() >= WORKSPACE_CLASS_SEARCH_LIMIT {
911 break;
912 }
913 }
914 out
915 };
916 let analysis = self
917 .cached_analysis_async_cancellable(uri, cancel_rev)
918 .await;
919 let session = self.docs.current_analysis_session();
920 let uri_owned = uri.clone();
921 let items = self
925 .blocking("completion", move || {
926 let ctx = CompletionCtx {
927 source: Some(&source),
928 position: Some(position),
929 doc_uri: Some(&uri_owned),
930 file_imports: Some(&imports),
931 find_class_doc: Some(&find_class_doc_fn),
932 workspace_class_search: Some(&workspace_class_search_fn),
933 analysis: analysis.as_deref(),
934 session: Some(session),
935 laravel: Some(&laravel_arc),
936 };
937 filtered_completions_at(&doc, &other_docs, trigger.as_deref(), &ctx)
938 })
939 .await
940 .unwrap_or_default();
941 Ok(Some(CompletionResponse::Array(items)))
942 })
943 .await
944 }
945
946 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
947 guard_async_result("completion_resolve", async move {
948 if item.documentation.is_some() && item.detail.is_some() {
949 return Ok(item);
950 }
951 let name = item.label.trim_end_matches(':').to_string();
953 let class_hint = item
957 .data
958 .as_ref()
959 .and_then(|d| d.get("class"))
960 .and_then(|v| v.as_str())
961 .map(str::to_string);
962 let symbol = completion_symbol_from_item(&name, item.kind, class_hint.as_deref());
963 let need_detail = item.detail.is_none();
964 let need_docs = item.documentation.is_none();
965 let docs = Arc::clone(&self.docs);
966 let (detail, documentation) = self
967 .blocking_gated(super::debug_gate::GATE_COMPLETION_RESOLVE, move || {
968 let Some(symbol) = symbol.as_ref() else {
969 return (None, None);
970 };
971 let session = docs.current_analysis_session();
972 let (resolved_detail, resolved_docstring) =
973 completion_signature_and_docstring_for_symbol(&session, symbol);
974 let detail = if need_detail {
975 resolved_detail.clone()
976 } else {
977 None
978 };
979 let documentation = need_docs
980 .then(|| {
981 match (resolved_detail.as_deref(), resolved_docstring.as_deref()) {
982 (Some(_), Some(doc)) if need_detail => Some(doc.to_string()),
983 (Some(sig), Some(doc)) => {
984 Some(format!("```php\n{sig}\n```\n\n---\n\n{doc}"))
985 }
986 (Some(sig), None) => Some(format!("```php\n{sig}\n```")),
987 (None, Some(doc)) => Some(doc.to_string()),
988 (None, None) => None,
989 }
990 })
991 .flatten();
992 (detail, documentation)
993 })
994 .await
995 .unwrap_or_default();
996 if let Some(sig) = detail {
997 item.detail = Some(sig);
998 }
999 if let Some(md) = documentation {
1000 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
1001 kind: MarkupKind::Markdown,
1002 value: md,
1003 }));
1004 }
1005 Ok(item)
1006 })
1007 .await
1008 }
1009
1010 async fn goto_definition(
1011 &self,
1012 params: GotoDefinitionParams,
1013 ) -> Result<Option<GotoDefinitionResponse>> {
1014 guard_async_result("goto_definition", async move {
1015 self.handle_goto_definition(params).await
1016 })
1017 .await
1018 }
1019
1020 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
1021 guard_async_result(
1022 "references",
1023 async move { self.handle_references(params).await },
1024 )
1025 .await
1026 }
1027
1028 async fn prepare_rename(
1029 &self,
1030 params: TextDocumentPositionParams,
1031 ) -> Result<Option<PrepareRenameResponse>> {
1032 guard_async_result("prepare_rename", async move {
1033 let uri = ¶ms.text_document.uri;
1034 let doc = match self.get_doc(uri) {
1035 Some(d) => d,
1036 None => return Ok(None),
1037 };
1038 let position = params.position;
1039 let range = self
1043 .blocking_gated(super::debug_gate::GATE_PREPARE_RENAME, move || {
1044 prepare_rename(&doc, position)
1045 })
1046 .await
1047 .unwrap_or_default();
1048 Ok(range.map(PrepareRenameResponse::Range))
1049 })
1050 .await
1051 }
1052
1053 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
1054 guard_async_result("rename", async move {
1055 let uri = ¶ms.text_document_position.text_document.uri;
1056 let position = params.text_document_position.position;
1057 let source = self.get_open_text(uri).unwrap_or_default();
1058 let word = match word_at_position(&source, position) {
1059 Some(w) => w,
1060 None => return Ok(None),
1061 };
1062 if word.starts_with('$') {
1063 let doc = match self.get_doc(uri) {
1064 Some(d) => d,
1065 None => return Ok(None),
1066 };
1067 let word_task = word.clone();
1074 let new_name = params.new_name.clone();
1075 let uri_task = uri.clone();
1076 let doc_task = Arc::clone(&doc);
1077 let source_task = Arc::clone(&source);
1078 let variable_edit = self
1079 .blocking_gated(super::debug_gate::GATE_RENAME_VARIABLE, move || {
1080 let on_property_decl = cursor_is_on_property_decl(
1081 &source_task,
1082 &doc_task.program().stmts,
1083 position,
1084 )
1085 .is_some()
1086 || promoted_property_at_cursor(
1087 &source_task,
1088 &doc_task.program().stmts,
1089 position,
1090 )
1091 .is_some();
1092 if on_property_decl {
1093 None
1094 } else {
1095 Some(rename_variable(
1096 &word_task, &new_name, &uri_task, &doc_task, position,
1097 ))
1098 }
1099 })
1100 .await
1101 .flatten();
1102 if let Some(edit) = variable_edit {
1103 return Ok(Some(edit));
1104 }
1105 }
1106 Ok(self.indexed_rename(uri, position, ¶ms.new_name).await)
1110 })
1111 .await
1112 }
1113
1114 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
1115 guard_async_result("signature_help", async move {
1116 let uri = ¶ms.text_document_position_params.text_document.uri;
1117 let position = params.text_document_position_params.position;
1118 let source = self.get_open_text(uri).unwrap_or_default();
1119 let doc = match self.get_doc(uri) {
1120 Some(d) => d,
1121 None => return Ok(None),
1122 };
1123 let analysis = self.cached_analysis_async(uri).await;
1124 let session = self.docs.current_analysis_session();
1125 let doc_clone = Arc::clone(&doc);
1126 let result = self
1127 .blocking("signature_help", move || {
1128 signature_help(
1129 &source,
1130 &doc_clone,
1131 position,
1132 analysis.as_deref(),
1133 Some(&session),
1134 )
1135 })
1136 .await
1137 .flatten();
1138 Ok(result)
1139 })
1140 .await
1141 }
1142
1143 #[tracing::instrument(skip_all)]
1144 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
1145 guard_async_result("hover", async move {
1146 let uri = ¶ms.text_document_position_params.text_document.uri;
1147 let position = params.text_document_position_params.position;
1148 let source = self.get_open_text(uri).unwrap_or_default();
1149 let doc = match self.get_doc(uri) {
1150 Some(d) => d,
1151 None => return Ok(None),
1152 };
1153 let laravel = self.laravel.load();
1158 let laravel_root = self.root_paths.load().first().cloned();
1159 let laravel_hover = crate::laravel::hover_for_string_key(
1160 &doc,
1161 position,
1162 &laravel,
1163 laravel_root.as_deref(),
1164 )
1165 .or_else(|| {
1166 crate::laravel::blade::hover(
1167 uri,
1168 &source,
1169 position,
1170 &laravel,
1171 laravel_root.as_deref(),
1172 )
1173 });
1174 drop(laravel);
1175 if let Some(hover) = laravel_hover {
1176 return Ok(Some(hover));
1177 }
1178 let open = self.open_urls();
1179 let other_docs = self.docs.other_docs(uri, &open);
1180 let other_maps = self.docs.other_symbol_maps(uri, &open);
1181 let analysis = self.cached_analysis_async(uri).await;
1182 let hover_session = self.docs.current_analysis_session();
1183 let hover_session_for_primary = Arc::clone(&hover_session);
1184 let source_clone = source.clone();
1185 let doc_clone = Arc::clone(&doc);
1186 let mut wi_cache: Option<Arc<crate::db::workspace_index::WorkspaceIndexData>> = None;
1193 let wi = self.workspace_index_cached(&mut wi_cache).await;
1194 let docs_for_lookup = Arc::clone(&self.docs);
1195 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
1196 let cr = if name.trim_start_matches('\\').contains('\\') {
1197 docs_for_lookup.resolve_class_ref_by_fqn_or_short_name_fallback(&wi, name)?
1198 } else {
1199 docs_for_lookup.resolve_class_ref_by_short_name(&wi, name)?
1200 };
1201 let (uri, _) = wi.at(cr)?;
1202 docs_for_lookup.get_doc_salsa(uri)
1203 };
1204 let result = self
1205 .blocking("hover", move || {
1206 hover_info_with_maps(
1207 &source_clone,
1208 &doc_clone,
1209 analysis.as_deref(),
1210 position,
1211 &other_docs,
1212 &other_maps,
1213 Some(&hover_session_for_primary),
1214 Some(&find_class_doc_fn),
1215 )
1216 })
1217 .await
1218 .flatten();
1219 if result.is_some() {
1220 return Ok(result);
1221 }
1222 if let Some(word) = crate::text::word_at_position(&source, position) {
1227 let wi = self.workspace_index_cached(&mut wi_cache).await;
1228 let resolve_class_ref_fallback = |name: &str| {
1229 if name.trim_start_matches('\\').contains('\\') {
1230 self.docs
1231 .resolve_class_ref_by_fqn_or_short_name_fallback(&wi, name)
1232 } else {
1233 self.docs.resolve_class_ref_by_short_name(&wi, name)
1234 }
1235 };
1236 let fallback_class_fqcn = |name: &str| {
1237 resolve_class_ref_fallback(name).and_then(|cr| {
1238 wi.at(cr)
1239 .map(|(_, cls)| cls.fqn.trim_start_matches('\\').to_string())
1240 })
1241 };
1242 if let Some(fqcn) = fallback_class_fqcn(&word)
1244 && let Some(h) = class_hover_for_fqcn(&hover_session, &fqcn)
1245 {
1246 return Ok(Some(h));
1247 }
1248 if let Some((resolved, resolved_fqn)) =
1252 crate::hover::resolve_use_alias_fqn(&doc.program().stmts, &word)
1253 && let Some(fqcn) = fallback_class_fqcn(&resolved)
1254 .filter(|fqcn| fqcn.eq_ignore_ascii_case(&resolved_fqn))
1255 .or(Some(resolved_fqn))
1256 && let Some(h) = class_hover_for_fqcn(&hover_session, &fqcn)
1257 {
1258 return Ok(Some(h));
1259 }
1260 if let Some(line_text) = source.lines().nth(position.line as usize)
1262 && let Some(class_token) =
1263 extract_static_class_before_cursor(line_text, position.character as usize)
1264 {
1265 if let Some(fqcn) = fallback_class_fqcn(&class_token)
1266 && let Some(h) = method_hover_for_fqcn(&hover_session, &fqcn, &word)
1267 {
1268 return Ok(Some(h));
1269 }
1270 if let Some(resolved_class) =
1271 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
1272 && let Some(fqcn) = fallback_class_fqcn(&resolved_class)
1273 && let Some(h) = method_hover_for_fqcn(&hover_session, &fqcn, &word)
1274 {
1275 return Ok(Some(h));
1276 }
1277 }
1278 }
1279 Ok(None)
1280 })
1281 .await
1282 }
1283
1284 async fn document_symbol(
1285 &self,
1286 params: DocumentSymbolParams,
1287 ) -> Result<Option<DocumentSymbolResponse>> {
1288 guard_async_result("document_symbol", async move {
1289 let uri = ¶ms.text_document.uri;
1290 let doc = match self.get_doc(uri) {
1291 Some(d) => d,
1292 None => return Ok(None),
1293 };
1294 let symbols = self
1296 .blocking_gated(super::debug_gate::GATE_DOCUMENT_SYMBOL, move || {
1297 document_symbols(doc.source(), &doc)
1298 })
1299 .await
1300 .unwrap_or_default();
1301 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1302 })
1303 .await
1304 }
1305
1306 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
1307 guard_async_result("folding_range", async move {
1308 let uri = ¶ms.text_document.uri;
1309 let doc = match self.get_doc(uri) {
1310 Some(d) => d,
1311 None => return Ok(None),
1312 };
1313 let ranges = self
1316 .blocking_gated(super::debug_gate::GATE_FOLDING_RANGE, move || {
1317 folding_ranges(doc.source(), &doc)
1318 })
1319 .await
1320 .unwrap_or_default();
1321 Ok(if ranges.is_empty() {
1322 None
1323 } else {
1324 Some(ranges)
1325 })
1326 })
1327 .await
1328 }
1329
1330 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
1331 guard_async_result("inlay_hint", async move {
1332 let uri = ¶ms.text_document.uri;
1333 let doc = match self.get_doc(uri) {
1334 Some(d) => d,
1335 None => return Ok(None),
1336 };
1337 let analysis = self.cached_analysis_async(uri).await;
1338 let session = self.docs.current_analysis_session();
1339 let hints = self
1340 .blocking("inlay_hint", move || {
1341 inlay_hints(
1342 doc.source(),
1343 &doc,
1344 analysis.as_deref(),
1345 Some(&session),
1346 params.range,
1347 )
1348 })
1349 .await
1350 .unwrap_or_default();
1351 Ok(Some(hints))
1352 })
1353 .await
1354 }
1355
1356 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
1357 let fallback = item.clone();
1358 let resolved = guard_async("inlay_hint_resolve", async move {
1359 if item.tooltip.is_some() {
1360 return Some(item);
1361 }
1362 let symbol = item.data.as_ref().and_then(inlay_hint_symbol_from_data);
1363 if let Some(symbol) = symbol {
1364 let docs = Arc::clone(&self.docs);
1365 let tooltip = self
1366 .blocking_gated(super::debug_gate::GATE_INLAY_HINT_RESOLVE, move || {
1367 let session = docs.current_analysis_session();
1368 inlay_hint_tooltip_for_symbol(&session, &symbol)
1369 })
1370 .await
1371 .flatten();
1372 if let Some(md) = tooltip {
1373 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
1374 kind: MarkupKind::Markdown,
1375 value: md,
1376 }));
1377 }
1378 }
1379 Some(item)
1380 })
1381 .await;
1382 Ok(resolved.unwrap_or(fallback))
1383 }
1384
1385 async fn symbol(
1386 &self,
1387 params: WorkspaceSymbolParams,
1388 ) -> Result<Option<WorkspaceSymbolResponse>> {
1389 guard_async_result("symbol", async move {
1390 let wi = self.workspace_index_async().await;
1394 let docs = Arc::clone(&self.docs);
1395 let query = params.query;
1396 let results = self
1397 .blocking("workspace/symbol", move || {
1398 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1399 workspace_symbols_from_workspace(&query, &wi, &get_doc)
1400 })
1401 .await
1402 .unwrap_or_default();
1403 Ok(Some(WorkspaceSymbolResponse::Flat(results)))
1404 })
1405 .await
1406 }
1407
1408 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
1409 let docs = self.docs.docs_for(&self.open_urls());
1411 let fallback = params.clone();
1412 let resolved = guard_async("symbol_resolve", async move {
1413 Some(resolve_workspace_symbol(params, &docs))
1414 })
1415 .await;
1416 Ok(resolved.unwrap_or(fallback))
1417 }
1418
1419 #[tracing::instrument(skip_all)]
1420 async fn semantic_tokens_full(
1421 &self,
1422 params: SemanticTokensParams,
1423 ) -> Result<Option<SemanticTokensResult>> {
1424 guard_async_result("semantic_tokens_full", async move {
1425 let uri = params.text_document.uri;
1426 let doc = match self.get_doc(&uri) {
1427 Some(d) => d,
1428 None => {
1429 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1430 result_id: None,
1431 data: vec![],
1432 })));
1433 }
1434 };
1435 let docs = Arc::clone(&self.docs);
1436 let uri_for_task = uri.clone();
1437 let (result_id, data) = self
1440 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_FULL, move || {
1441 let tokens = semantic_tokens(doc.source(), &doc);
1442 let result_id = token_hash(&tokens);
1443 let tokens_arc = Arc::new(tokens);
1444 docs.store_token_cache(
1445 &uri_for_task,
1446 result_id.clone(),
1447 Arc::clone(&tokens_arc),
1448 );
1449 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
1450 (result_id, data)
1451 })
1452 .await
1453 .unwrap_or_default();
1454 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1455 result_id: Some(result_id),
1456 data,
1457 })))
1458 })
1459 .await
1460 }
1461
1462 async fn semantic_tokens_range(
1463 &self,
1464 params: SemanticTokensRangeParams,
1465 ) -> Result<Option<SemanticTokensRangeResult>> {
1466 guard_async_result("semantic_tokens_range", async move {
1467 let uri = params.text_document.uri;
1468 let doc = match self.get_doc(&uri) {
1469 Some(d) => d,
1470 None => {
1471 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1472 result_id: None,
1473 data: vec![],
1474 })));
1475 }
1476 };
1477 let range = params.range;
1478 let tokens = self
1479 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_RANGE, move || {
1480 semantic_tokens_range(doc.source(), &doc, range)
1481 })
1482 .await
1483 .unwrap_or_default();
1484 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1485 result_id: None,
1486 data: tokens,
1487 })))
1488 })
1489 .await
1490 }
1491
1492 async fn semantic_tokens_full_delta(
1493 &self,
1494 params: SemanticTokensDeltaParams,
1495 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
1496 guard_async_result("semantic_tokens_full_delta", async move {
1497 let uri = params.text_document.uri;
1498 let doc = match self.get_doc(&uri) {
1499 Some(d) => d,
1500 None => return Ok(None),
1501 };
1502
1503 let docs = Arc::clone(&self.docs);
1504 let uri_for_task = uri.clone();
1505 let prev_id = params.previous_result_id;
1506 let result = self
1507 .blocking_gated(
1508 super::debug_gate::GATE_SEMANTIC_TOKENS_FULL_DELTA,
1509 move || {
1510 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
1511 let new_result_id = token_hash(&new_tokens);
1512
1513 let result = match docs.get_token_cache(&uri_for_task, &prev_id) {
1514 Some(old_tokens) => {
1515 let edits = compute_token_delta(&old_tokens, &new_tokens);
1516 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
1517 result_id: Some(new_result_id.clone()),
1518 edits,
1519 })
1520 }
1521 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
1523 result_id: Some(new_result_id.clone()),
1524 data: (*new_tokens).clone(),
1525 }),
1526 };
1527
1528 docs.store_token_cache(&uri_for_task, new_result_id, new_tokens);
1529 result
1530 },
1531 )
1532 .await;
1533 let Some(result) = result else {
1534 return Ok(None);
1535 };
1536 Ok(Some(result))
1537 })
1538 .await
1539 }
1540
1541 async fn selection_range(
1542 &self,
1543 params: SelectionRangeParams,
1544 ) -> Result<Option<Vec<SelectionRange>>> {
1545 guard_async_result("selection_range", async move {
1546 let uri = ¶ms.text_document.uri;
1547 let doc = match self.get_doc(uri) {
1548 Some(d) => d,
1549 None => return Ok(None),
1550 };
1551 let ranges = self
1555 .blocking_gated(super::debug_gate::GATE_SELECTION_RANGE, move || {
1556 selection_ranges(&doc, ¶ms.positions)
1557 })
1558 .await
1559 .unwrap_or_default();
1560 Ok(if ranges.is_empty() {
1561 None
1562 } else {
1563 Some(ranges)
1564 })
1565 })
1566 .await
1567 }
1568
1569 async fn prepare_call_hierarchy(
1570 &self,
1571 params: CallHierarchyPrepareParams,
1572 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1573 guard_async_result("prepare_call_hierarchy", async move {
1574 let uri = ¶ms.text_document_position_params.text_document.uri;
1575 let position = params.text_document_position_params.position;
1576 let source = self.get_open_text(uri).unwrap_or_default();
1577 let word = match word_at_position(&source, position) {
1578 Some(w) => w,
1579 None => return Ok(None),
1580 };
1581 if is_unresolvable_bareword_at(&source, position, &word) {
1585 return Ok(None);
1586 }
1587 let wi = self.workspace_index_async().await;
1592 let docs = Arc::clone(&self.docs);
1593 let item = self
1594 .blocking_gated(super::debug_gate::GATE_PREPARE_CALL_HIERARCHY, move || {
1595 let get_doc = |u: &Uri| docs.get_doc_salsa(u);
1596 let mention_candidates =
1597 |name: &str| docs.declaration_candidate_files(&wi, name);
1598 prepare_call_hierarchy_indexed(&word, &wi, &get_doc, &mention_candidates)
1599 })
1600 .await
1601 .flatten();
1602 Ok(item.map(|item| vec![item]))
1603 })
1604 .await
1605 }
1606
1607 async fn incoming_calls(
1608 &self,
1609 params: CallHierarchyIncomingCallsParams,
1610 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1611 guard_async_result("incoming_calls", async move {
1612 let docs = Arc::clone(&self.docs);
1615 let item = params.item;
1616 let calls = self
1617 .blocking("incoming_calls", move || {
1618 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
1621 incoming_calls_indexed(&item, &docs, Some(cancel_rev))
1622 })
1623 .await
1624 .unwrap_or_default();
1625 Ok(if calls.is_empty() { None } else { Some(calls) })
1626 })
1627 .await
1628 }
1629
1630 async fn outgoing_calls(
1631 &self,
1632 params: CallHierarchyOutgoingCallsParams,
1633 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1634 guard_async_result("outgoing_calls", async move {
1635 let wi = self.workspace_index_async().await;
1639 let docs = Arc::clone(&self.docs);
1640 let item = params.item;
1641 let calls = self
1642 .blocking("outgoing_calls", move || {
1643 let get_doc = |u: &Uri| docs.get_doc_salsa(u);
1644 let mention_candidates =
1645 |name: &str| docs.declaration_candidate_files(&wi, name);
1646 outgoing_calls_indexed(&item, &wi, &get_doc, &mention_candidates)
1647 })
1648 .await
1649 .unwrap_or_default();
1650 Ok(if calls.is_empty() { None } else { Some(calls) })
1651 })
1652 .await
1653 }
1654
1655 async fn document_highlight(
1656 &self,
1657 params: DocumentHighlightParams,
1658 ) -> Result<Option<Vec<DocumentHighlight>>> {
1659 guard_async_result("document_highlight", async move {
1660 let uri = ¶ms.text_document_position_params.text_document.uri;
1661 let position = params.text_document_position_params.position;
1662 let source = self.get_open_text(uri).unwrap_or_default();
1663 let doc = match self.get_doc_stale(uri) {
1666 Some(d) => d,
1667 None => return Ok(None),
1668 };
1669 let highlights = self
1671 .blocking("document_highlight", move || {
1672 document_highlights(&source, &doc, position)
1673 })
1674 .await
1675 .unwrap_or_default();
1676 Ok(if highlights.is_empty() {
1677 None
1678 } else {
1679 Some(highlights)
1680 })
1681 })
1682 .await
1683 }
1684
1685 async fn linked_editing_range(
1686 &self,
1687 params: LinkedEditingRangeParams,
1688 ) -> Result<Option<LinkedEditingRanges>> {
1689 guard_async_result("linked_editing_range", async move {
1690 self.handle_linked_editing_range(params).await
1691 })
1692 .await
1693 }
1694
1695 async fn goto_implementation(
1696 &self,
1697 params: tower_lsp_server::ls_types::request::GotoImplementationParams,
1698 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoImplementationResponse>> {
1699 guard_async_result("goto_implementation", async move {
1700 let uri = ¶ms.text_document_position_params.text_document.uri;
1701 let position = params.text_document_position_params.position;
1702 let source = self.get_open_text(uri).unwrap_or_default();
1703 let imports = self.file_imports(uri);
1704 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
1705 if raw_word.is_empty() || is_unresolvable_bareword_at(&source, position, &raw_word) {
1706 return Ok(None);
1707 }
1708 let (word, fqn): (String, String) = if raw_word.contains('\\') {
1711 let short = raw_word
1712 .rsplit('\\')
1713 .next()
1714 .unwrap_or(&raw_word)
1715 .to_string();
1716 (short, raw_word.trim_start_matches('\\').to_string())
1717 } else {
1718 let resolved = match self.get_doc(uri) {
1722 Some(doc) => crate::navigation::moniker::resolve_fqn(&doc, &raw_word, &imports),
1723 None => raw_word.clone(),
1724 };
1725 (raw_word, resolved.trim_start_matches('\\').to_string())
1726 };
1727
1728 let doc_for_method_check = self.get_doc(uri);
1734 let on_method_decl = self
1735 .blocking_gated(super::debug_gate::GATE_GOTO_IMPLEMENTATION, move || {
1736 doc_for_method_check.is_some_and(|doc| {
1737 cursor_is_on_method_decl(doc.source(), &doc.program().stmts, position)
1738 })
1739 })
1740 .await
1741 .unwrap_or(false);
1742
1743 let mut locs: Vec<Location> = if on_method_decl {
1746 Vec::new()
1747 } else {
1748 let docs = Arc::clone(&self.docs);
1749 let fqn_task = fqn.clone();
1750 self.blocking("goto_implementation.subtypes", move || {
1751 docs.indexed_subtype_classes(&fqn_task, false)
1752 })
1753 .await
1754 .unwrap_or_default()
1755 .into_iter()
1756 .filter_map(|site| subtype_site_to_location(&site.file, &site.range))
1757 .collect()
1758 };
1759
1760 if locs.is_empty()
1763 && let Some(doc) = self.get_doc(uri)
1764 && let Some(enclosing) =
1765 crate::types::type_map::enclosing_class_at(&source, &doc, position)
1766 {
1767 let enclosing_fqn =
1768 crate::navigation::moniker::resolve_fqn(&doc, &enclosing, &imports)
1769 .trim_start_matches('\\')
1770 .to_string();
1771 let docs = Arc::clone(&self.docs);
1772 let method = word.clone();
1773 let impls = self
1774 .blocking("goto_implementation.method_impls", move || {
1775 docs.indexed_method_implementations(&enclosing_fqn, &method)
1776 })
1777 .await
1778 .unwrap_or_default();
1779 locs = impls
1780 .into_iter()
1781 .filter_map(|(_, file, range)| subtype_site_to_location(&file, &range))
1782 .collect();
1783 }
1784 if locs.is_empty() {
1785 Ok(None)
1786 } else {
1787 Ok(Some(GotoDefinitionResponse::Array(locs)))
1788 }
1789 })
1790 .await
1791 }
1792
1793 async fn goto_declaration(
1794 &self,
1795 params: tower_lsp_server::ls_types::request::GotoDeclarationParams,
1796 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoDeclarationResponse>> {
1797 guard_async_result("goto_declaration", async move {
1798 let uri = ¶ms.text_document_position_params.text_document.uri;
1799 let position = params.text_document_position_params.position;
1800 let source = self.get_open_text(uri).unwrap_or_default();
1801 let open_docs = self.docs.docs_for(&self.open_urls());
1807 let source_for_pass1 = source.clone();
1808 let first_pass = self
1809 .blocking("goto_declaration.open_docs", move || {
1810 goto_declaration(&source_for_pass1, &open_docs, position)
1811 })
1812 .await
1813 .unwrap_or_default();
1814 if let Some(loc) = first_pass {
1815 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1816 }
1817 let wi = self.workspace_index_async().await;
1820 let docs = Arc::clone(&self.docs);
1821 let second_pass = self
1822 .blocking("goto_declaration.index", move || {
1823 let mention_candidates =
1824 |name: &str| docs.declaration_candidate_files(&wi, name);
1825 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1826 goto_declaration_from_index(
1827 &source,
1828 &wi,
1829 position,
1830 &get_doc,
1831 &mention_candidates,
1832 )
1833 })
1834 .await
1835 .unwrap_or_default();
1836 Ok(second_pass.map(GotoDefinitionResponse::Scalar))
1837 })
1838 .await
1839 }
1840
1841 async fn goto_type_definition(
1842 &self,
1843 params: tower_lsp_server::ls_types::request::GotoTypeDefinitionParams,
1844 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoTypeDefinitionResponse>> {
1845 guard_async_result("goto_type_definition", async move {
1846 let uri = ¶ms.text_document_position_params.text_document.uri;
1847 let position = params.text_document_position_params.position;
1848 let source = self.get_open_text(uri).unwrap_or_default();
1849 let doc = match self.get_doc(uri) {
1850 Some(d) => d,
1851 None => return Ok(None),
1852 };
1853 let analysis = self.cached_analysis_async(uri).await;
1854 let open_docs = self.docs.docs_for(&self.open_urls());
1855 let docs = Arc::clone(&self.docs);
1856 let wi = self.workspace_index_async().await;
1857
1858 let response = self
1863 .blocking_gated(super::debug_gate::GATE_GOTO_TYPE_DEFINITION, move || {
1864 let mut results = goto_type_definition_exact(
1869 &source,
1870 &doc,
1871 analysis.as_deref(),
1872 &open_docs,
1873 position,
1874 );
1875 if results.is_empty() {
1876 let exact_uri = |fqn: &str| {
1877 docs.class_ref_by_fqn(&wi, fqn)
1878 .and_then(|cr| wi.at(cr).map(|(uri, _)| uri.clone()))
1879 };
1880 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1881 results = goto_type_definition_from_index_exact(
1882 &source,
1883 &doc,
1884 analysis.as_deref(),
1885 position,
1886 &exact_uri,
1887 &get_doc,
1888 );
1889 }
1890 if results.is_empty() {
1891 results = goto_type_definition_short_name_fallback(
1892 &source,
1893 &doc,
1894 analysis.as_deref(),
1895 &open_docs,
1896 position,
1897 );
1898 }
1899 if results.is_empty() {
1900 let class_candidate_uris = |short: &str| {
1901 docs.class_candidates_by_short_name(&wi, short)
1902 .into_iter()
1903 .filter_map(|cr| wi.at(cr).map(|(uri, _)| uri.clone()))
1904 .collect()
1905 };
1906 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1907 results = goto_type_definition_from_index_short_name_fallback(
1908 &source,
1909 &doc,
1910 analysis.as_deref(),
1911 position,
1912 &class_candidate_uris,
1913 &get_doc,
1914 );
1915 }
1916
1917 match results.len() {
1919 0 => None,
1920 1 => Some(GotoDefinitionResponse::Scalar(
1921 results.into_iter().next().unwrap(),
1922 )),
1923 _ => Some(GotoDefinitionResponse::Array(results)),
1924 }
1925 })
1926 .await
1927 .unwrap_or_default();
1928 Ok(response)
1929 })
1930 .await
1931 }
1932
1933 async fn prepare_type_hierarchy(
1934 &self,
1935 params: TypeHierarchyPrepareParams,
1936 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1937 guard_async_result("prepare_type_hierarchy", async move {
1938 let uri = ¶ms.text_document_position_params.text_document.uri;
1939 let position = params.text_document_position_params.position;
1940 let source = self.get_open_text(uri).unwrap_or_default();
1941 let wi = self.workspace_index_async().await;
1943 let docs = Arc::clone(&self.docs);
1944 let uri = uri.clone();
1945 let item = self
1946 .blocking_gated(super::debug_gate::GATE_TYPE_HIERARCHY, move || {
1947 let class_candidates =
1948 |short: &str| docs.class_candidates_by_short_name(&wi, short);
1949 prepare_type_hierarchy_from_workspace(
1950 &source,
1951 &uri,
1952 &wi,
1953 position,
1954 &class_candidates,
1955 )
1956 })
1957 .await
1958 .flatten();
1959 Ok(item.map(|item| vec![item]))
1960 })
1961 .await
1962 }
1963
1964 async fn supertypes(
1965 &self,
1966 params: TypeHierarchySupertypesParams,
1967 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1968 guard_async_result("supertypes", async move {
1969 let wi = self.workspace_index_async().await;
1973 let loaded_new = self
1974 .ensure_direct_supertypes_loaded(
1975 ¶ms.item.name,
1976 crate::navigation::type_hierarchy::item_fqn(¶ms.item),
1977 &wi,
1978 )
1979 .await;
1980 let wi = if loaded_new {
1981 self.workspace_index_async().await
1982 } else {
1983 wi
1984 };
1985 let docs = Arc::clone(&self.docs);
1986 let item = params.item;
1987 let result = self
1988 .blocking_gated(super::debug_gate::GATE_TYPE_HIERARCHY, move || {
1989 let class_candidates =
1990 |short: &str| docs.class_candidates_by_short_name(&wi, short);
1991 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1992 let resolve_class_ref =
1993 |fqn: &str| docs.resolve_class_ref_by_fqn_or_short_name_fallback(&wi, fqn);
1994 supertypes_of_from_workspace(
1995 &item,
1996 &wi,
1997 &class_candidates,
1998 &get_doc,
1999 &resolve_class_ref,
2000 )
2001 })
2002 .await
2003 .unwrap_or_default();
2004 Ok(if result.is_empty() {
2005 None
2006 } else {
2007 Some(result)
2008 })
2009 })
2010 .await
2011 }
2012
2013 async fn subtypes(
2014 &self,
2015 params: TypeHierarchySubtypesParams,
2016 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
2017 guard_async_result("subtypes", async move {
2018 let wi = self.workspace_index_async().await;
2019 let docs = Arc::clone(&self.docs);
2020 let item = params.item;
2021 let result = self
2022 .blocking_gated(super::debug_gate::GATE_TYPE_HIERARCHY, move || {
2023 let item_fqn = crate::navigation::type_hierarchy::item_fqn(&item)
2024 .map(str::to_string)
2025 .or_else(|| {
2026 let candidates = docs.class_candidates_by_short_name(&wi, &item.name);
2028 candidates
2029 .iter()
2030 .filter_map(|r| wi.at(*r))
2031 .find(|(u, _)| **u == item.uri)
2032 .or_else(|| candidates.first().and_then(|r| wi.at(*r)))
2033 .map(|(_, cls)| cls.fqn.as_ref().to_string())
2034 });
2035 let subtype_urls = item_fqn
2036 .as_deref()
2037 .map(|f| docs.class_subtype_urls(f))
2038 .unwrap_or_default();
2039 let mention_candidates =
2040 |name: &str| docs.declaration_candidate_files(&wi, name);
2041 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
2042 subtypes_of_mir_backed(
2043 &item,
2044 item_fqn.as_deref(),
2045 &wi,
2046 &subtype_urls,
2047 &mention_candidates,
2048 &get_doc,
2049 )
2050 })
2051 .await
2052 .unwrap_or_default();
2053 Ok(if result.is_empty() {
2054 None
2055 } else {
2056 Some(result)
2057 })
2058 })
2059 .await
2060 }
2061
2062 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
2063 guard_async_result("code_lens", async move {
2064 let uri = ¶ms.text_document.uri;
2065 let doc = match self.get_doc(uri) {
2066 Some(d) => d,
2067 None => return Ok(None),
2068 };
2069 let docs = Arc::clone(&self.docs);
2075 let docs_cancel = Arc::clone(&self.docs);
2076 let uri_owned = uri.clone();
2077 let imports = self.file_imports(uri);
2078 let lenses = self
2079 .blocking("code_lens", move || {
2080 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
2083 code_lenses(
2084 &uri_owned,
2085 &doc,
2086 &docs,
2087 &imports,
2088 Some(cancel_rev),
2089 move || docs_cancel.write_rev() != cancel_rev,
2090 )
2091 })
2092 .await
2093 .unwrap_or_default();
2094 Ok(if lenses.is_empty() {
2095 None
2096 } else {
2097 Some(lenses)
2098 })
2099 })
2100 .await
2101 }
2102
2103 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
2104 let fallback = params.clone();
2105 let resolved = guard_async("code_lens_resolve", async move {
2106 Some(params)
2108 })
2109 .await;
2110 Ok(resolved.unwrap_or(fallback))
2111 }
2112
2113 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
2114 guard_async_result("document_link", async move {
2115 let uri = params.text_document.uri;
2116 let doc = match self.get_doc(&uri) {
2117 Some(d) => d,
2118 None => return Ok(None),
2119 };
2120 let laravel = self.laravel.load_full();
2121 let links = self
2124 .blocking_gated(super::debug_gate::GATE_DOCUMENT_LINK, move || {
2125 let mut links = document_links(&uri, &doc, doc.source());
2126 links.extend(crate::laravel::document_links(&doc, &laravel));
2127 links.extend(crate::laravel::blade::document_links(
2128 &uri,
2129 doc.source(),
2130 &laravel,
2131 ));
2132 links
2133 })
2134 .await
2135 .unwrap_or_default();
2136 Ok(if links.is_empty() { None } else { Some(links) })
2137 })
2138 .await
2139 }
2140
2141 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
2142 let fallback = params.clone();
2143 let resolved = guard_async("document_link_resolve", async move {
2144 Some(params)
2146 })
2147 .await;
2148 Ok(resolved.unwrap_or(fallback))
2149 }
2150
2151 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
2152 guard_async_result("formatting", async move {
2153 let uri = ¶ms.text_document.uri;
2154 let source = self.get_open_text(uri).unwrap_or_default();
2155 Ok(self
2158 .blocking("formatting", move || format_document(&source))
2159 .await
2160 .unwrap_or_default())
2161 })
2162 .await
2163 }
2164
2165 async fn range_formatting(
2166 &self,
2167 params: DocumentRangeFormattingParams,
2168 ) -> Result<Option<Vec<TextEdit>>> {
2169 guard_async_result("range_formatting", async move {
2170 let uri = ¶ms.text_document.uri;
2171 let source = self.get_open_text(uri).unwrap_or_default();
2172 let range = params.range;
2173 Ok(self
2174 .blocking("range_formatting", move || format_range(&source, range))
2175 .await
2176 .unwrap_or_default())
2177 })
2178 .await
2179 }
2180
2181 async fn on_type_formatting(
2182 &self,
2183 params: DocumentOnTypeFormattingParams,
2184 ) -> Result<Option<Vec<TextEdit>>> {
2185 guard_async_result("on_type_formatting", async move {
2186 let uri = ¶ms.text_document_position.text_document.uri;
2187 let source = self.get_open_text(uri).unwrap_or_default();
2188 let position = params.text_document_position.position;
2189 let edits = self
2194 .blocking_gated(super::debug_gate::GATE_ON_TYPE_FORMATTING, move || {
2195 on_type_format(&source, position, ¶ms.ch, ¶ms.options)
2196 })
2197 .await
2198 .unwrap_or_default();
2199 Ok(if edits.is_empty() { None } else { Some(edits) })
2200 })
2201 .await
2202 }
2203
2204 async fn execute_command(
2205 &self,
2206 params: ExecuteCommandParams,
2207 ) -> Result<Option<serde_json::Value>> {
2208 guard_async_result("execute_command", async move {
2209 match params.command.as_str() {
2210 "php-lsp.runTest" => {
2211 let file_uri = params
2213 .arguments
2214 .first()
2215 .and_then(|v| v.as_str())
2216 .and_then(|s| (s).parse::<Uri>().ok());
2217 let filter = params
2218 .arguments
2219 .get(1)
2220 .and_then(|v| v.as_str())
2221 .unwrap_or("")
2222 .to_string();
2223
2224 let root = self.root_paths.load().first().cloned();
2225 let client = self.client.clone();
2226
2227 tokio::spawn(async move {
2228 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
2229 });
2230
2231 Ok(None)
2232 }
2233 _ => Ok(None),
2234 }
2235 })
2236 .await
2237 }
2238
2239 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
2240 guard_async_result("will_rename_files", async move {
2241 self.handle_will_rename_files(params).await
2242 })
2243 .await
2244 }
2245
2246 async fn did_rename_files(&self, params: RenameFilesParams) {
2247 guard_async("did_rename_files", async move {
2248 self.handle_did_rename_files(params).await
2249 })
2250 .await
2251 }
2252
2253 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
2254 guard_async_result("will_create_files", async move {
2255 self.handle_will_create_files(params).await
2256 })
2257 .await
2258 }
2259
2260 async fn did_create_files(&self, params: CreateFilesParams) {
2261 guard_async("did_create_files", async move {
2262 self.handle_did_create_files(params).await
2263 })
2264 .await
2265 }
2266
2267 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
2270 guard_async_result("will_delete_files", async move {
2271 self.handle_will_delete_files(params).await
2272 })
2273 .await
2274 }
2275
2276 async fn did_delete_files(&self, params: DeleteFilesParams) {
2277 guard_async("did_delete_files", async move {
2278 self.handle_did_delete_files(params).await
2279 })
2280 .await
2281 }
2282
2283 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
2284 guard_async_result("moniker", async move {
2285 let uri = ¶ms.text_document_position_params.text_document.uri;
2286 let position = params.text_document_position_params.position;
2287 let source = self.get_open_text(uri).unwrap_or_default();
2288 let doc = match self.get_doc(uri) {
2289 Some(d) => d,
2290 None => return Ok(None),
2291 };
2292 let imports = self.file_imports(uri);
2293 let moniker = self
2294 .blocking_gated(super::debug_gate::GATE_MONIKER, move || {
2295 moniker_at(&source, &doc, position, &imports)
2296 })
2297 .await
2298 .flatten();
2299 Ok(moniker.map(|m| vec![m]))
2300 })
2301 .await
2302 }
2303
2304 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
2305 guard_async_result("inline_value", async move {
2306 let uri = ¶ms.text_document.uri;
2307 let source = self.get_open_text(uri).unwrap_or_default();
2308 let range = params.range;
2309 let values = self
2310 .blocking_gated(super::debug_gate::GATE_INLINE_VALUE, move || {
2311 inline_values_in_range(&source, range)
2312 })
2313 .await
2314 .unwrap_or_default();
2315 Ok(if values.is_empty() {
2316 None
2317 } else {
2318 Some(values)
2319 })
2320 })
2321 .await
2322 }
2323
2324 async fn diagnostic(
2325 &self,
2326 params: DocumentDiagnosticParams,
2327 ) -> Result<DocumentDiagnosticReportResult> {
2328 let report = guard_async("diagnostic", async move {
2329 Some(self.handle_diagnostic(params).await)
2330 })
2331 .await;
2332 match report {
2333 Some(r) => r,
2334 None => Ok(DocumentDiagnosticReportResult::Report(
2335 DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
2336 )),
2337 }
2338 }
2339
2340 async fn workspace_diagnostic(
2341 &self,
2342 params: WorkspaceDiagnosticParams,
2343 ) -> Result<WorkspaceDiagnosticReportResult> {
2344 let report = guard_async("workspace_diagnostic", async move {
2345 Some(self.handle_workspace_diagnostic(params).await)
2346 })
2347 .await;
2348 match report {
2349 Some(r) => r,
2350 None => Ok(WorkspaceDiagnosticReportResult::Report(
2351 WorkspaceDiagnosticReport::default(),
2352 )),
2353 }
2354 }
2355
2356 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
2357 guard_async_result("code_action", async move {
2358 self.handle_code_action(params).await
2359 })
2360 .await
2361 }
2362
2363 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
2364 guard_async_result("code_action_resolve", async move {
2365 self.handle_code_action_resolve(item).await
2366 })
2367 .await
2368 }
2369}
2370
2371fn subtype_site_to_location(file: &str, range: &mir_analyzer::Range) -> Option<Location> {
2374 let uri = (file).parse::<Uri>().ok()?;
2375 let line = range.start.line.saturating_sub(1);
2376 Some(Location {
2377 uri,
2378 range: tower_lsp_server::ls_types::Range {
2379 start: tower_lsp_server::ls_types::Position {
2380 line,
2381 character: range.start.column,
2382 },
2383 end: tower_lsp_server::ls_types::Position {
2384 line,
2385 character: range.end.column,
2386 },
2387 },
2388 })
2389}