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 open_urls = open_files.urls();
425 let ex = exclude_paths.clone();
426 let ip = include_paths.clone();
427 let path_clone = path.clone();
428 let client = self.client.clone();
429 let cp = cache_path.clone();
430 let warm_analysis = self.config.load().warm_analysis;
431 tokio::spawn(async move {
432 let cache = if let Some(p) = cp {
433 Some(crate::index::cache::WorkspaceCache::with_dir(p))
434 } else {
435 crate::index::cache::WorkspaceCache::new(&path_clone)
436 };
437 scan_workspace(
438 path_clone,
439 Arc::clone(&docs),
440 open_files,
441 cache,
442 &ex,
443 &ip,
444 max_indexed_files,
445 None,
446 )
447 .await;
448 super::offload::run("workspaceFolders.warmStart", move || {
455 docs.get_workspace_index_salsa();
456 let untrusted = docs.warm_start_indexes();
457 docs.warm_references_phase(
458 untrusted,
459 &open_urls,
460 warm_analysis,
461 None,
462 );
463 })
464 .await;
465 send_refresh_requests(&client).await;
466 });
467 }
468 }
469 }
470 })
471 .await
472 }
473
474 async fn shutdown(&self) -> Result<()> {
475 let docs = Arc::clone(&self.docs);
478 self.blocking("shutdown.flush", move || docs.flush_analysis_cache())
479 .await;
480 Ok(())
481 }
482
483 #[tracing::instrument(skip_all)]
484 async fn did_open(&self, params: DidOpenTextDocumentParams) {
485 guard_async("did_open", async move {
486 let uri = params.text_document.uri;
487 let text = params.text_document.text;
488
489 self.set_open_text(uri.clone(), text);
493 self.docs.note_new_file_declarations(&uri);
500
501 let docs = Arc::clone(&self.docs);
505 let uri_for_task = uri.clone();
506 let parse_diags = self
507 .blocking_gated(super::debug_gate::GATE_DID_OPEN_PARSE, move || {
508 docs.get_doc_salsa(&uri_for_task)
509 .map(|doc| diagnostics_from_doc(&doc))
510 .unwrap_or_default()
511 })
512 .await
513 .unwrap_or_default();
514 self.set_parse_diagnostics(&uri, parse_diags);
515
516 publish_with_dependents(
517 self.client.clone(),
518 Arc::clone(&self.docs),
519 self.open_files.clone(),
520 uri,
521 self.config.load().diagnostics.clone(),
522 self.laravel.load_full().is_laravel,
523 )
524 .await;
525 })
526 .await
527 }
528
529 #[tracing::instrument(skip_all)]
530 async fn did_change(&self, params: DidChangeTextDocumentParams) {
531 guard_async("did_change", async move {
532 let uri = params.text_document.uri;
533 let mut updated: Option<String> = None;
538 for change in params.content_changes {
539 match change.range {
540 None => updated = Some(change.text),
541 Some(range) => {
542 let mut cur = match updated.take() {
543 Some(t) => t,
544 None => self.get_open_text(&uri).unwrap_or_default().to_string(),
545 };
546 crate::text::apply_content_change(&mut cur, range, &change.text);
547 updated = Some(cur);
548 }
549 }
550 }
551 let Some(text) = updated else { return };
552
553 let version = self.set_open_text(uri.clone(), text.clone());
557
558 let docs = Arc::clone(&self.docs);
559 let open_files = self.open_files.clone();
560 let client = self.client.clone();
561 let cfg = self.config.load();
562 let diag_cfg = cfg.diagnostics.clone();
563 let debounce_ms = cfg.debounce_ms;
564 let warm_analysis = cfg.warm_analysis;
565 let is_laravel = self.laravel.load_full().is_laravel;
566 tokio::spawn(async move {
567 tokio::time::sleep(std::time::Duration::from_millis(debounce_ms)).await;
570
571 if open_files.current_version(&uri) != Some(version) {
575 return;
576 }
577
578 let (_doc, parse_diags) =
579 super::offload::run("did_change.parse", move || parse_document(&text))
580 .await
581 .unwrap_or_default();
582
583 if open_files.current_version(&uri) != Some(version) {
585 return;
586 }
587 open_files.set_parse_diagnostics(&uri, parse_diags);
588 publish_with_dependents(
589 client,
590 Arc::clone(&docs),
591 open_files.clone(),
592 uri.clone(),
593 diag_cfg,
594 is_laravel,
595 )
596 .await;
597
598 if warm_analysis && docs.is_index_ready() {
603 tokio::time::sleep(std::time::Duration::from_millis(WARM_RESWEEP_IDLE_MS))
604 .await;
605 if open_files.current_version(&uri) != Some(version) {
606 return;
607 }
608 let open_urls = open_files.urls();
609 drop(tokio::task::spawn_blocking(move || {
614 let cancel = docs.begin_warm_sweep();
615 docs.warm_analysis_sweep(&open_urls, &cancel);
616 }));
617 }
618 });
619 })
620 .await
621 }
622
623 async fn did_close(&self, params: DidCloseTextDocumentParams) {
624 guard_async("did_close", async move {
625 let uri = params.text_document.uri;
626 self.close_open_file(&uri);
627 let disk_text = match uri.to_file_path() {
632 Some(path) => tokio::fs::read_to_string(&path).await.ok(),
633 None => None,
634 };
635 if !self.open_files.contains(&uri) {
639 match disk_text {
640 Some(text) => self.docs.ingest(uri.clone(), &text),
641 None => self.docs.remove(&uri),
642 }
643 }
644 self.client.publish_diagnostics(uri, vec![], None).await;
646 })
647 .await
648 }
649
650 async fn will_save(&self, _params: WillSaveTextDocumentParams) {}
651
652 async fn will_save_wait_until(
653 &self,
654 params: WillSaveTextDocumentParams,
655 ) -> Result<Option<Vec<TextEdit>>> {
656 guard_async_result("will_save_wait_until", async move {
657 let source = self
658 .get_open_text(¶ms.text_document.uri)
659 .unwrap_or_default();
660 Ok(self
663 .blocking("willSaveWaitUntil.format", move || format_document(&source))
664 .await
665 .unwrap_or_default())
666 })
667 .await
668 }
669
670 async fn did_save(&self, params: DidSaveTextDocumentParams) {
671 guard_async("did_save", async move {
672 let uri = params.text_document.uri;
673 let diag_cfg = self.config.load().diagnostics.clone();
679 let is_laravel = self.laravel.load_full().is_laravel;
680 let docs = Arc::clone(&self.docs);
684 let open_files = self.open_files.clone();
685 let uri_for_diags = uri.clone();
686 let all = self
687 .blocking_gated(super::debug_gate::GATE_DID_SAVE_DIAGNOSTICS, move || {
688 compute_open_file_diagnostics(
689 &docs,
690 &open_files,
691 &uri_for_diags,
692 &diag_cfg,
693 is_laravel,
694 )
695 })
696 .await
697 .unwrap_or_default();
698 self.open_files
699 .note_published(&uri, super::diagnostics_content_hash(&all));
700 self.client
701 .publish_diagnostics(uri.clone(), all, None)
702 .await;
703
704 let cache_path = self.config.load().cache_path.clone();
709 if let Some(path) = uri.to_file_path().map(|c| c.into_owned()) {
710 let root = self.root_paths.load().first().cloned();
711 tokio::task::spawn_blocking({
715 let path = path.clone();
716 let uri = uri.clone();
717 let root = root.clone();
718 move || {
719 let cache = if let Some(p) = cache_path {
720 crate::index::cache::WorkspaceCache::with_dir(p)
721 } else if let Some(r) = &root {
722 let Some(c) = crate::index::cache::WorkspaceCache::new(r) else {
723 return;
724 };
725 c
726 } else {
727 return;
728 };
729 let Ok(text) = std::fs::read_to_string(&path) else {
730 return;
731 };
732 let key = crate::index::cache::WorkspaceCache::key_for(uri.as_str(), &text);
733 let doc = parse_document_no_diags(&text);
734 let index = crate::index::file_index::FileIndex::extract(&doc);
735 let _ = cache.write(&key, &index);
736 }
737 });
738
739 let ext_cfg = self.config.load().external_tools.clone();
747 if (ext_cfg.phpstan.enabled || ext_cfg.phpcs.enabled)
748 && let Some(version) = self.open_files.current_version(&uri)
749 {
750 let client = self.client.clone();
751 let docs = Arc::clone(&self.docs);
752 let open_files = self.open_files.clone();
753 let diag_cfg = self.config.load().diagnostics.clone();
754 let uri = uri.clone();
755 tokio::spawn(async move {
756 let diagnostics = crate::analysis::external::run_external_diagnostics(
757 &ext_cfg,
758 &path,
759 root.as_deref(),
760 )
761 .await;
762 if open_files.current_version(&uri) != Some(version) {
763 return;
764 }
765 open_files.set_external_diagnostics(&uri, version, diagnostics);
766 publish_with_dependents(
767 client, docs, open_files, uri, diag_cfg, is_laravel,
768 )
769 .await;
770 });
771 }
772 }
773 })
774 .await
775 }
776
777 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
778 guard_async("did_change_watched_files", async move {
779 enum Change {
780 Upsert(Uri, String),
781 Delete(Uri),
782 }
783 let mut changes = Vec::with_capacity(params.changes.len());
784 for change in params.changes {
785 match change.typ {
786 FileChangeType::CREATED | FileChangeType::CHANGED => {
787 if let Some(path) = change.uri.to_file_path()
788 && let Ok(text) = tokio::fs::read_to_string(&path).await
789 {
790 changes.push(Change::Upsert(change.uri.clone(), text));
791 }
792 }
793 FileChangeType::DELETED => changes.push(Change::Delete(change.uri.clone())),
794 _ => {}
795 }
796 }
797
798 let docs = Arc::clone(&self.docs);
803 let open_files = self.open_files.clone();
804 self.blocking_gated(
805 super::debug_gate::GATE_DID_CHANGE_WATCHED_FILES,
806 move || {
807 for change in changes {
808 match change {
809 Change::Upsert(uri, text) => {
810 let doc = parse_document_no_diags(&text);
815 if !open_files.contains(&uri) {
816 docs.ingest_from_doc(uri.clone(), &doc);
817 }
818 docs.note_new_file_declarations(&uri);
822 }
823 Change::Delete(uri) => docs.remove(&uri),
824 }
825 }
826 },
827 )
828 .await;
829 send_refresh_requests(&self.client).await;
831
832 if self.config.load().warm_analysis && self.docs.is_index_ready() {
836 let docs = Arc::clone(&self.docs);
837 let open_urls = self.open_files.urls();
838 drop(tokio::task::spawn_blocking(move || {
841 let cancel = docs.begin_warm_sweep();
842 docs.warm_analysis_sweep(&open_urls, &cancel);
843 }));
844 }
845 })
846 .await
847 }
848
849 #[tracing::instrument(skip_all)]
850 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
851 guard_async_result("completion", async move {
852 let _interactive = self.docs.interactive_read_guard();
856 let uri = ¶ms.text_document_position.text_document.uri;
857 let position = params.text_document_position.position;
858 let cancel_rev = self.docs.write_rev();
859 let source = self.get_open_text(uri).unwrap_or_default();
860 let doc = match self.get_doc(uri) {
861 Some(d) => d,
862 None => return Ok(Some(CompletionResponse::Array(vec![]))),
863 };
864 let other_docs: Vec<Arc<ParsedDoc>> = self
865 .docs
866 .other_docs(uri, &self.open_urls())
867 .into_iter()
868 .map(|(_, d)| d)
869 .collect();
870 let trigger = params
872 .context
873 .as_ref()
874 .and_then(|c| c.trigger_character.clone());
875 let laravel_arc = self.laravel.load_full();
876 let imports = self.file_imports(uri);
877 let wi = self.workspace_index_async().await;
878 let wi_for_class_search = Arc::clone(&wi);
879 let uri_for_class_search = uri.clone();
880 let docs_for_lookup = Arc::clone(&self.docs);
881 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
882 let cr = if name.trim_start_matches('\\').contains('\\') {
883 docs_for_lookup.resolve_class_ref_by_fqn_or_short_name_fallback(&wi, name)?
884 } else {
885 docs_for_lookup.resolve_class_ref_by_short_name(&wi, name)?
886 };
887 let (uri, _) = wi.at(cr)?;
888 docs_for_lookup.get_doc_salsa(uri)
889 };
890 const WORKSPACE_CLASS_SEARCH_LIMIT: usize = 50;
901 let workspace_class_search_fn =
902 move |prefix: &str| -> Vec<(String, CompletionItemKind, String)> {
903 let prefix_lc = prefix.to_lowercase();
904 let table = &wi_for_class_search.classes_by_lowercase_name;
905 let start =
906 table.partition_point(|(name, _)| name.as_ref() < prefix_lc.as_str());
907 let mut out = Vec::new();
908 for (name, cr) in &table[start..] {
909 if !name.starts_with(prefix_lc.as_str()) {
910 break;
911 }
912 let Some((file_uri, cls)) = wi_for_class_search.at(*cr) else {
913 continue;
914 };
915 if *file_uri == uri_for_class_search {
916 continue;
917 }
918 let kind = match cls.kind {
919 ClassKind::Class | ClassKind::Trait => CompletionItemKind::CLASS,
920 ClassKind::Interface => CompletionItemKind::INTERFACE,
921 ClassKind::Enum => CompletionItemKind::ENUM,
922 };
923 out.push((cls.name.to_string(), kind, cls.fqn.to_string()));
924 if out.len() >= WORKSPACE_CLASS_SEARCH_LIMIT {
925 break;
926 }
927 }
928 out
929 };
930 let analysis = self
931 .cached_analysis_async_cancellable(uri, cancel_rev)
932 .await;
933 let session = self.docs.current_analysis_session();
934 let uri_owned = uri.clone();
935 let items = self
939 .blocking("completion", move || {
940 let ctx = CompletionCtx {
941 source: Some(&source),
942 position: Some(position),
943 doc_uri: Some(&uri_owned),
944 file_imports: Some(&imports),
945 find_class_doc: Some(&find_class_doc_fn),
946 workspace_class_search: Some(&workspace_class_search_fn),
947 analysis: analysis.as_deref(),
948 session: Some(session),
949 laravel: Some(&laravel_arc),
950 };
951 filtered_completions_at(&doc, &other_docs, trigger.as_deref(), &ctx)
952 })
953 .await
954 .unwrap_or_default();
955 Ok(Some(CompletionResponse::Array(items)))
956 })
957 .await
958 }
959
960 async fn completion_resolve(&self, mut item: CompletionItem) -> Result<CompletionItem> {
961 guard_async_result("completion_resolve", async move {
962 if item.documentation.is_some() && item.detail.is_some() {
963 return Ok(item);
964 }
965 let name = item.label.trim_end_matches(':').to_string();
967 let class_hint = item
971 .data
972 .as_ref()
973 .and_then(|d| d.get("class"))
974 .and_then(|v| v.as_str())
975 .map(str::to_string);
976 let symbol = completion_symbol_from_item(&name, item.kind, class_hint.as_deref());
977 let need_detail = item.detail.is_none();
978 let need_docs = item.documentation.is_none();
979 let docs = Arc::clone(&self.docs);
980 let (detail, documentation) = self
981 .blocking_gated(super::debug_gate::GATE_COMPLETION_RESOLVE, move || {
982 let Some(symbol) = symbol.as_ref() else {
983 return (None, None);
984 };
985 let session = docs.current_analysis_session();
986 let (resolved_detail, resolved_docstring) =
987 completion_signature_and_docstring_for_symbol(&session, symbol);
988 let detail = if need_detail {
989 resolved_detail.clone()
990 } else {
991 None
992 };
993 let documentation = need_docs
994 .then(|| {
995 match (resolved_detail.as_deref(), resolved_docstring.as_deref()) {
996 (Some(_), Some(doc)) if need_detail => Some(doc.to_string()),
997 (Some(sig), Some(doc)) => {
998 Some(format!("```php\n{sig}\n```\n\n---\n\n{doc}"))
999 }
1000 (Some(sig), None) => Some(format!("```php\n{sig}\n```")),
1001 (None, Some(doc)) => Some(doc.to_string()),
1002 (None, None) => None,
1003 }
1004 })
1005 .flatten();
1006 (detail, documentation)
1007 })
1008 .await
1009 .unwrap_or_default();
1010 if let Some(sig) = detail {
1011 item.detail = Some(sig);
1012 }
1013 if let Some(md) = documentation {
1014 item.documentation = Some(Documentation::MarkupContent(MarkupContent {
1015 kind: MarkupKind::Markdown,
1016 value: md,
1017 }));
1018 }
1019 Ok(item)
1020 })
1021 .await
1022 }
1023
1024 async fn goto_definition(
1025 &self,
1026 params: GotoDefinitionParams,
1027 ) -> Result<Option<GotoDefinitionResponse>> {
1028 guard_async_result("goto_definition", async move {
1029 self.handle_goto_definition(params).await
1030 })
1031 .await
1032 }
1033
1034 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
1035 guard_async_result(
1036 "references",
1037 async move { self.handle_references(params).await },
1038 )
1039 .await
1040 }
1041
1042 async fn prepare_rename(
1043 &self,
1044 params: TextDocumentPositionParams,
1045 ) -> Result<Option<PrepareRenameResponse>> {
1046 guard_async_result("prepare_rename", async move {
1047 let uri = ¶ms.text_document.uri;
1048 let doc = match self.get_doc(uri) {
1049 Some(d) => d,
1050 None => return Ok(None),
1051 };
1052 let position = params.position;
1053 let range = self
1057 .blocking_gated(super::debug_gate::GATE_PREPARE_RENAME, move || {
1058 prepare_rename(&doc, position)
1059 })
1060 .await
1061 .unwrap_or_default();
1062 Ok(range.map(PrepareRenameResponse::Range))
1063 })
1064 .await
1065 }
1066
1067 async fn rename(&self, params: RenameParams) -> Result<Option<WorkspaceEdit>> {
1068 guard_async_result("rename", async move {
1069 let uri = ¶ms.text_document_position.text_document.uri;
1070 let position = params.text_document_position.position;
1071 let source = self.get_open_text(uri).unwrap_or_default();
1072 let word = match word_at_position(&source, position) {
1073 Some(w) => w,
1074 None => return Ok(None),
1075 };
1076 if word.starts_with('$') {
1077 let doc = match self.get_doc(uri) {
1078 Some(d) => d,
1079 None => return Ok(None),
1080 };
1081 let word_task = word.clone();
1088 let new_name = params.new_name.clone();
1089 let uri_task = uri.clone();
1090 let doc_task = Arc::clone(&doc);
1091 let source_task = Arc::clone(&source);
1092 let variable_edit = self
1093 .blocking_gated(super::debug_gate::GATE_RENAME_VARIABLE, move || {
1094 let on_property_decl = cursor_is_on_property_decl(
1095 &source_task,
1096 &doc_task.program().stmts,
1097 position,
1098 )
1099 .is_some()
1100 || promoted_property_at_cursor(
1101 &source_task,
1102 &doc_task.program().stmts,
1103 position,
1104 )
1105 .is_some();
1106 if on_property_decl {
1107 None
1108 } else {
1109 Some(rename_variable(
1110 &word_task, &new_name, &uri_task, &doc_task, position,
1111 ))
1112 }
1113 })
1114 .await
1115 .flatten();
1116 if let Some(edit) = variable_edit {
1117 return Ok(Some(edit));
1118 }
1119 }
1120 Ok(self.indexed_rename(uri, position, ¶ms.new_name).await)
1124 })
1125 .await
1126 }
1127
1128 async fn signature_help(&self, params: SignatureHelpParams) -> Result<Option<SignatureHelp>> {
1129 guard_async_result("signature_help", async move {
1130 let uri = ¶ms.text_document_position_params.text_document.uri;
1131 let position = params.text_document_position_params.position;
1132 let source = self.get_open_text(uri).unwrap_or_default();
1133 let doc = match self.get_doc(uri) {
1134 Some(d) => d,
1135 None => return Ok(None),
1136 };
1137 let analysis = self.cached_analysis_async(uri).await;
1138 let session = self.docs.current_analysis_session();
1139 let doc_clone = Arc::clone(&doc);
1140 let result = self
1141 .blocking("signature_help", move || {
1142 signature_help(
1143 &source,
1144 &doc_clone,
1145 position,
1146 analysis.as_deref(),
1147 Some(&session),
1148 )
1149 })
1150 .await
1151 .flatten();
1152 Ok(result)
1153 })
1154 .await
1155 }
1156
1157 #[tracing::instrument(skip_all)]
1158 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
1159 guard_async_result("hover", async move {
1160 let _interactive = self.docs.interactive_read_guard();
1164 let uri = ¶ms.text_document_position_params.text_document.uri;
1165 let position = params.text_document_position_params.position;
1166 let source = self.get_open_text(uri).unwrap_or_default();
1167 let doc = match self.get_doc(uri) {
1168 Some(d) => d,
1169 None => return Ok(None),
1170 };
1171 let laravel = self.laravel.load();
1176 let laravel_root = self.root_paths.load().first().cloned();
1177 let laravel_hover = crate::laravel::hover_for_string_key(
1178 &doc,
1179 position,
1180 &laravel,
1181 laravel_root.as_deref(),
1182 )
1183 .or_else(|| {
1184 crate::laravel::blade::hover(
1185 uri,
1186 &source,
1187 position,
1188 &laravel,
1189 laravel_root.as_deref(),
1190 )
1191 });
1192 drop(laravel);
1193 if let Some(hover) = laravel_hover {
1194 return Ok(Some(hover));
1195 }
1196 let open = self.open_urls();
1197 let other_docs = self.docs.other_docs(uri, &open);
1198 let other_maps = self.docs.other_symbol_maps(uri, &open);
1199 let analysis = self.cached_analysis_async(uri).await;
1200 let hover_session = self.docs.current_analysis_session();
1201 let hover_session_for_primary = Arc::clone(&hover_session);
1202 let source_clone = source.clone();
1203 let doc_clone = Arc::clone(&doc);
1204 let mut wi_cache: Option<Arc<crate::db::workspace_index::WorkspaceIndexData>> = None;
1211 let wi = self.workspace_index_cached(&mut wi_cache).await;
1212 let docs_for_lookup = Arc::clone(&self.docs);
1213 let find_class_doc_fn = move |name: &str| -> Option<Arc<ParsedDoc>> {
1214 let cr = if name.trim_start_matches('\\').contains('\\') {
1215 docs_for_lookup.resolve_class_ref_by_fqn_or_short_name_fallback(&wi, name)?
1216 } else {
1217 docs_for_lookup.resolve_class_ref_by_short_name(&wi, name)?
1218 };
1219 let (uri, _) = wi.at(cr)?;
1220 docs_for_lookup.get_doc_salsa(uri)
1221 };
1222 let result = self
1223 .blocking("hover", move || {
1224 hover_info_with_maps(
1225 &source_clone,
1226 &doc_clone,
1227 analysis.as_deref(),
1228 position,
1229 &other_docs,
1230 &other_maps,
1231 Some(&hover_session_for_primary),
1232 Some(&find_class_doc_fn),
1233 )
1234 })
1235 .await
1236 .flatten();
1237 if result.is_some() {
1238 return Ok(result);
1239 }
1240 if let Some(word) = crate::text::word_at_position(&source, position) {
1245 let wi = self.workspace_index_cached(&mut wi_cache).await;
1246 let resolve_class_ref_fallback = |name: &str| {
1247 if name.trim_start_matches('\\').contains('\\') {
1248 self.docs
1249 .resolve_class_ref_by_fqn_or_short_name_fallback(&wi, name)
1250 } else {
1251 self.docs.resolve_class_ref_by_short_name(&wi, name)
1252 }
1253 };
1254 let fallback_class_fqcn = |name: &str| {
1255 resolve_class_ref_fallback(name).and_then(|cr| {
1256 wi.at(cr)
1257 .map(|(_, cls)| cls.fqn.trim_start_matches('\\').to_string())
1258 })
1259 };
1260 if let Some(fqcn) = fallback_class_fqcn(&word)
1262 && let Some(h) = class_hover_for_fqcn(&hover_session, &fqcn)
1263 {
1264 return Ok(Some(h));
1265 }
1266 if let Some((resolved, resolved_fqn)) =
1270 crate::hover::resolve_use_alias_fqn(&doc.program().stmts, &word)
1271 && let Some(fqcn) = fallback_class_fqcn(&resolved)
1272 .filter(|fqcn| fqcn.eq_ignore_ascii_case(&resolved_fqn))
1273 .or(Some(resolved_fqn))
1274 && let Some(h) = class_hover_for_fqcn(&hover_session, &fqcn)
1275 {
1276 return Ok(Some(h));
1277 }
1278 if let Some(line_text) = source.lines().nth(position.line as usize)
1280 && let Some(class_token) =
1281 extract_static_class_before_cursor(line_text, position.character as usize)
1282 {
1283 if let Some(fqcn) = fallback_class_fqcn(&class_token)
1284 && let Some(h) = method_hover_for_fqcn(&hover_session, &fqcn, &word)
1285 {
1286 return Ok(Some(h));
1287 }
1288 if let Some(resolved_class) =
1289 crate::hover::resolve_use_alias(&doc.program().stmts, &class_token)
1290 && let Some(fqcn) = fallback_class_fqcn(&resolved_class)
1291 && let Some(h) = method_hover_for_fqcn(&hover_session, &fqcn, &word)
1292 {
1293 return Ok(Some(h));
1294 }
1295 }
1296 }
1297 Ok(None)
1298 })
1299 .await
1300 }
1301
1302 async fn document_symbol(
1303 &self,
1304 params: DocumentSymbolParams,
1305 ) -> Result<Option<DocumentSymbolResponse>> {
1306 guard_async_result("document_symbol", async move {
1307 let uri = ¶ms.text_document.uri;
1308 let doc = match self.get_doc(uri) {
1309 Some(d) => d,
1310 None => return Ok(None),
1311 };
1312 let symbols = self
1314 .blocking_gated(super::debug_gate::GATE_DOCUMENT_SYMBOL, move || {
1315 document_symbols(doc.source(), &doc)
1316 })
1317 .await
1318 .unwrap_or_default();
1319 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1320 })
1321 .await
1322 }
1323
1324 async fn folding_range(&self, params: FoldingRangeParams) -> Result<Option<Vec<FoldingRange>>> {
1325 guard_async_result("folding_range", async move {
1326 let uri = ¶ms.text_document.uri;
1327 let doc = match self.get_doc(uri) {
1328 Some(d) => d,
1329 None => return Ok(None),
1330 };
1331 let ranges = self
1334 .blocking_gated(super::debug_gate::GATE_FOLDING_RANGE, move || {
1335 folding_ranges(doc.source(), &doc)
1336 })
1337 .await
1338 .unwrap_or_default();
1339 Ok(if ranges.is_empty() {
1340 None
1341 } else {
1342 Some(ranges)
1343 })
1344 })
1345 .await
1346 }
1347
1348 async fn inlay_hint(&self, params: InlayHintParams) -> Result<Option<Vec<InlayHint>>> {
1349 guard_async_result("inlay_hint", async move {
1350 let uri = ¶ms.text_document.uri;
1351 let doc = match self.get_doc(uri) {
1352 Some(d) => d,
1353 None => return Ok(None),
1354 };
1355 let analysis = self.cached_analysis_async(uri).await;
1356 let session = self.docs.current_analysis_session();
1357 let hints = self
1358 .blocking("inlay_hint", move || {
1359 inlay_hints(
1360 doc.source(),
1361 &doc,
1362 analysis.as_deref(),
1363 Some(&session),
1364 params.range,
1365 )
1366 })
1367 .await
1368 .unwrap_or_default();
1369 Ok(Some(hints))
1370 })
1371 .await
1372 }
1373
1374 async fn inlay_hint_resolve(&self, mut item: InlayHint) -> Result<InlayHint> {
1375 let fallback = item.clone();
1376 let resolved = guard_async("inlay_hint_resolve", async move {
1377 if item.tooltip.is_some() {
1378 return Some(item);
1379 }
1380 let symbol = item.data.as_ref().and_then(inlay_hint_symbol_from_data);
1381 if let Some(symbol) = symbol {
1382 let docs = Arc::clone(&self.docs);
1383 let tooltip = self
1384 .blocking_gated(super::debug_gate::GATE_INLAY_HINT_RESOLVE, move || {
1385 let session = docs.current_analysis_session();
1386 inlay_hint_tooltip_for_symbol(&session, &symbol)
1387 })
1388 .await
1389 .flatten();
1390 if let Some(md) = tooltip {
1391 item.tooltip = Some(InlayHintTooltip::MarkupContent(MarkupContent {
1392 kind: MarkupKind::Markdown,
1393 value: md,
1394 }));
1395 }
1396 }
1397 Some(item)
1398 })
1399 .await;
1400 Ok(resolved.unwrap_or(fallback))
1401 }
1402
1403 async fn symbol(
1404 &self,
1405 params: WorkspaceSymbolParams,
1406 ) -> Result<Option<WorkspaceSymbolResponse>> {
1407 guard_async_result("symbol", async move {
1408 let wi = self.workspace_index_async().await;
1412 let docs = Arc::clone(&self.docs);
1413 let query = params.query;
1414 let results = self
1415 .blocking("workspace/symbol", move || {
1416 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1417 workspace_symbols_from_workspace(&query, &wi, &get_doc)
1418 })
1419 .await
1420 .unwrap_or_default();
1421 Ok(Some(WorkspaceSymbolResponse::Flat(results)))
1422 })
1423 .await
1424 }
1425
1426 async fn symbol_resolve(&self, params: WorkspaceSymbol) -> Result<WorkspaceSymbol> {
1427 let docs = self.docs.docs_for(&self.open_urls());
1429 let fallback = params.clone();
1430 let resolved = guard_async("symbol_resolve", async move {
1431 Some(resolve_workspace_symbol(params, &docs))
1432 })
1433 .await;
1434 Ok(resolved.unwrap_or(fallback))
1435 }
1436
1437 #[tracing::instrument(skip_all)]
1438 async fn semantic_tokens_full(
1439 &self,
1440 params: SemanticTokensParams,
1441 ) -> Result<Option<SemanticTokensResult>> {
1442 guard_async_result("semantic_tokens_full", async move {
1443 let uri = params.text_document.uri;
1444 let doc = match self.get_doc(&uri) {
1445 Some(d) => d,
1446 None => {
1447 return Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1448 result_id: None,
1449 data: vec![],
1450 })));
1451 }
1452 };
1453 let docs = Arc::clone(&self.docs);
1454 let uri_for_task = uri.clone();
1455 let (result_id, data) = self
1458 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_FULL, move || {
1459 let tokens = semantic_tokens(doc.source(), &doc);
1460 let result_id = token_hash(&tokens);
1461 let tokens_arc = Arc::new(tokens);
1462 docs.store_token_cache(
1463 &uri_for_task,
1464 result_id.clone(),
1465 Arc::clone(&tokens_arc),
1466 );
1467 let data = Arc::try_unwrap(tokens_arc).unwrap_or_else(|arc| (*arc).clone());
1468 (result_id, data)
1469 })
1470 .await
1471 .unwrap_or_default();
1472 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1473 result_id: Some(result_id),
1474 data,
1475 })))
1476 })
1477 .await
1478 }
1479
1480 async fn semantic_tokens_range(
1481 &self,
1482 params: SemanticTokensRangeParams,
1483 ) -> Result<Option<SemanticTokensRangeResult>> {
1484 guard_async_result("semantic_tokens_range", async move {
1485 let uri = params.text_document.uri;
1486 let doc = match self.get_doc(&uri) {
1487 Some(d) => d,
1488 None => {
1489 return Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1490 result_id: None,
1491 data: vec![],
1492 })));
1493 }
1494 };
1495 let range = params.range;
1496 let tokens = self
1497 .blocking_gated(super::debug_gate::GATE_SEMANTIC_TOKENS_RANGE, move || {
1498 semantic_tokens_range(doc.source(), &doc, range)
1499 })
1500 .await
1501 .unwrap_or_default();
1502 Ok(Some(SemanticTokensRangeResult::Tokens(SemanticTokens {
1503 result_id: None,
1504 data: tokens,
1505 })))
1506 })
1507 .await
1508 }
1509
1510 async fn semantic_tokens_full_delta(
1511 &self,
1512 params: SemanticTokensDeltaParams,
1513 ) -> Result<Option<SemanticTokensFullDeltaResult>> {
1514 guard_async_result("semantic_tokens_full_delta", async move {
1515 let uri = params.text_document.uri;
1516 let doc = match self.get_doc(&uri) {
1517 Some(d) => d,
1518 None => return Ok(None),
1519 };
1520
1521 let docs = Arc::clone(&self.docs);
1522 let uri_for_task = uri.clone();
1523 let prev_id = params.previous_result_id;
1524 let result = self
1525 .blocking_gated(
1526 super::debug_gate::GATE_SEMANTIC_TOKENS_FULL_DELTA,
1527 move || {
1528 let new_tokens = Arc::new(semantic_tokens(doc.source(), &doc));
1529 let new_result_id = token_hash(&new_tokens);
1530
1531 let result = match docs.get_token_cache(&uri_for_task, &prev_id) {
1532 Some(old_tokens) => {
1533 let edits = compute_token_delta(&old_tokens, &new_tokens);
1534 SemanticTokensFullDeltaResult::TokensDelta(SemanticTokensDelta {
1535 result_id: Some(new_result_id.clone()),
1536 edits,
1537 })
1538 }
1539 None => SemanticTokensFullDeltaResult::Tokens(SemanticTokens {
1541 result_id: Some(new_result_id.clone()),
1542 data: (*new_tokens).clone(),
1543 }),
1544 };
1545
1546 docs.store_token_cache(&uri_for_task, new_result_id, new_tokens);
1547 result
1548 },
1549 )
1550 .await;
1551 let Some(result) = result else {
1552 return Ok(None);
1553 };
1554 Ok(Some(result))
1555 })
1556 .await
1557 }
1558
1559 async fn selection_range(
1560 &self,
1561 params: SelectionRangeParams,
1562 ) -> Result<Option<Vec<SelectionRange>>> {
1563 guard_async_result("selection_range", async move {
1564 let uri = ¶ms.text_document.uri;
1565 let doc = match self.get_doc(uri) {
1566 Some(d) => d,
1567 None => return Ok(None),
1568 };
1569 let ranges = self
1573 .blocking_gated(super::debug_gate::GATE_SELECTION_RANGE, move || {
1574 selection_ranges(&doc, ¶ms.positions)
1575 })
1576 .await
1577 .unwrap_or_default();
1578 Ok(if ranges.is_empty() {
1579 None
1580 } else {
1581 Some(ranges)
1582 })
1583 })
1584 .await
1585 }
1586
1587 async fn prepare_call_hierarchy(
1588 &self,
1589 params: CallHierarchyPrepareParams,
1590 ) -> Result<Option<Vec<CallHierarchyItem>>> {
1591 guard_async_result("prepare_call_hierarchy", async move {
1592 let uri = ¶ms.text_document_position_params.text_document.uri;
1593 let position = params.text_document_position_params.position;
1594 let source = self.get_open_text(uri).unwrap_or_default();
1595 let word = match word_at_position(&source, position) {
1596 Some(w) => w,
1597 None => return Ok(None),
1598 };
1599 if is_unresolvable_bareword_at(&source, position, &word) {
1603 return Ok(None);
1604 }
1605 let wi = self.workspace_index_async().await;
1610 let docs = Arc::clone(&self.docs);
1611 let item = self
1612 .blocking_gated(super::debug_gate::GATE_PREPARE_CALL_HIERARCHY, move || {
1613 let get_doc = |u: &Uri| docs.get_doc_salsa(u);
1614 let mention_candidates =
1615 |name: &str| docs.declaration_candidate_files(&wi, name);
1616 prepare_call_hierarchy_indexed(&word, &wi, &get_doc, &mention_candidates)
1617 })
1618 .await
1619 .flatten();
1620 Ok(item.map(|item| vec![item]))
1621 })
1622 .await
1623 }
1624
1625 async fn incoming_calls(
1626 &self,
1627 params: CallHierarchyIncomingCallsParams,
1628 ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
1629 guard_async_result("incoming_calls", async move {
1630 let docs = Arc::clone(&self.docs);
1633 let item = params.item;
1634 let calls = self
1635 .blocking("incoming_calls", move || {
1636 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
1639 incoming_calls_indexed(&item, &docs, Some(cancel_rev))
1640 })
1641 .await
1642 .unwrap_or_default();
1643 Ok(if calls.is_empty() { None } else { Some(calls) })
1644 })
1645 .await
1646 }
1647
1648 async fn outgoing_calls(
1649 &self,
1650 params: CallHierarchyOutgoingCallsParams,
1651 ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
1652 guard_async_result("outgoing_calls", async move {
1653 let wi = self.workspace_index_async().await;
1657 let docs = Arc::clone(&self.docs);
1658 let item = params.item;
1659 let calls = self
1660 .blocking("outgoing_calls", move || {
1661 let get_doc = |u: &Uri| docs.get_doc_salsa(u);
1662 let mention_candidates =
1663 |name: &str| docs.declaration_candidate_files(&wi, name);
1664 outgoing_calls_indexed(&item, &wi, &get_doc, &mention_candidates)
1665 })
1666 .await
1667 .unwrap_or_default();
1668 Ok(if calls.is_empty() { None } else { Some(calls) })
1669 })
1670 .await
1671 }
1672
1673 async fn document_highlight(
1674 &self,
1675 params: DocumentHighlightParams,
1676 ) -> Result<Option<Vec<DocumentHighlight>>> {
1677 guard_async_result("document_highlight", async move {
1678 let uri = ¶ms.text_document_position_params.text_document.uri;
1679 let position = params.text_document_position_params.position;
1680 let source = self.get_open_text(uri).unwrap_or_default();
1681 let doc = match self.get_doc_stale(uri) {
1684 Some(d) => d,
1685 None => return Ok(None),
1686 };
1687 let highlights = self
1689 .blocking("document_highlight", move || {
1690 document_highlights(&source, &doc, position)
1691 })
1692 .await
1693 .unwrap_or_default();
1694 Ok(if highlights.is_empty() {
1695 None
1696 } else {
1697 Some(highlights)
1698 })
1699 })
1700 .await
1701 }
1702
1703 async fn linked_editing_range(
1704 &self,
1705 params: LinkedEditingRangeParams,
1706 ) -> Result<Option<LinkedEditingRanges>> {
1707 guard_async_result("linked_editing_range", async move {
1708 self.handle_linked_editing_range(params).await
1709 })
1710 .await
1711 }
1712
1713 async fn goto_implementation(
1714 &self,
1715 params: tower_lsp_server::ls_types::request::GotoImplementationParams,
1716 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoImplementationResponse>> {
1717 guard_async_result("goto_implementation", async move {
1718 let uri = ¶ms.text_document_position_params.text_document.uri;
1719 let position = params.text_document_position_params.position;
1720 let source = self.get_open_text(uri).unwrap_or_default();
1721 let imports = self.file_imports(uri);
1722 let raw_word = crate::text::word_at_position(&source, position).unwrap_or_default();
1723 if raw_word.is_empty() || is_unresolvable_bareword_at(&source, position, &raw_word) {
1724 return Ok(None);
1725 }
1726 let (word, fqn): (String, String) = if raw_word.contains('\\') {
1729 let short = raw_word
1730 .rsplit('\\')
1731 .next()
1732 .unwrap_or(&raw_word)
1733 .to_string();
1734 (short, raw_word.trim_start_matches('\\').to_string())
1735 } else {
1736 let resolved = match self.get_doc(uri) {
1740 Some(doc) => crate::navigation::moniker::resolve_fqn(&doc, &raw_word, &imports),
1741 None => raw_word.clone(),
1742 };
1743 (raw_word, resolved.trim_start_matches('\\').to_string())
1744 };
1745
1746 let doc_for_method_check = self.get_doc(uri);
1752 let on_method_decl = self
1753 .blocking_gated(super::debug_gate::GATE_GOTO_IMPLEMENTATION, move || {
1754 doc_for_method_check.is_some_and(|doc| {
1755 cursor_is_on_method_decl(doc.source(), &doc.program().stmts, position)
1756 })
1757 })
1758 .await
1759 .unwrap_or(false);
1760
1761 let mut locs: Vec<Location> = if on_method_decl {
1764 Vec::new()
1765 } else {
1766 let docs = Arc::clone(&self.docs);
1767 let fqn_task = fqn.clone();
1768 self.blocking("goto_implementation.subtypes", move || {
1769 docs.indexed_subtype_classes(&fqn_task, false)
1770 })
1771 .await
1772 .unwrap_or_default()
1773 .into_iter()
1774 .filter_map(|site| subtype_site_to_location(&site.file, &site.range))
1775 .collect()
1776 };
1777
1778 if locs.is_empty()
1781 && let Some(doc) = self.get_doc(uri)
1782 && let Some(enclosing) =
1783 crate::types::type_map::enclosing_class_at(&source, &doc, position)
1784 {
1785 let enclosing_fqn =
1786 crate::navigation::moniker::resolve_fqn(&doc, &enclosing, &imports)
1787 .trim_start_matches('\\')
1788 .to_string();
1789 let docs = Arc::clone(&self.docs);
1790 let method = word.clone();
1791 let impls = self
1792 .blocking("goto_implementation.method_impls", move || {
1793 docs.indexed_method_implementations(&enclosing_fqn, &method)
1794 })
1795 .await
1796 .unwrap_or_default();
1797 locs = impls
1798 .into_iter()
1799 .filter_map(|(_, file, range)| subtype_site_to_location(&file, &range))
1800 .collect();
1801 }
1802 if locs.is_empty() {
1803 Ok(None)
1804 } else {
1805 Ok(Some(GotoDefinitionResponse::Array(locs)))
1806 }
1807 })
1808 .await
1809 }
1810
1811 async fn goto_declaration(
1812 &self,
1813 params: tower_lsp_server::ls_types::request::GotoDeclarationParams,
1814 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoDeclarationResponse>> {
1815 guard_async_result("goto_declaration", async move {
1816 let uri = ¶ms.text_document_position_params.text_document.uri;
1817 let position = params.text_document_position_params.position;
1818 let source = self.get_open_text(uri).unwrap_or_default();
1819 let open_docs = self.docs.docs_for(&self.open_urls());
1825 let source_for_pass1 = source.clone();
1826 let first_pass = self
1827 .blocking("goto_declaration.open_docs", move || {
1828 goto_declaration(&source_for_pass1, &open_docs, position)
1829 })
1830 .await
1831 .unwrap_or_default();
1832 if let Some(loc) = first_pass {
1833 return Ok(Some(GotoDefinitionResponse::Scalar(loc)));
1834 }
1835 let wi = self.workspace_index_async().await;
1838 let docs = Arc::clone(&self.docs);
1839 let second_pass = self
1840 .blocking("goto_declaration.index", move || {
1841 let mention_candidates =
1842 |name: &str| docs.declaration_candidate_files(&wi, name);
1843 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1844 goto_declaration_from_index(
1845 &source,
1846 &wi,
1847 position,
1848 &get_doc,
1849 &mention_candidates,
1850 )
1851 })
1852 .await
1853 .unwrap_or_default();
1854 Ok(second_pass.map(GotoDefinitionResponse::Scalar))
1855 })
1856 .await
1857 }
1858
1859 async fn goto_type_definition(
1860 &self,
1861 params: tower_lsp_server::ls_types::request::GotoTypeDefinitionParams,
1862 ) -> Result<Option<tower_lsp_server::ls_types::request::GotoTypeDefinitionResponse>> {
1863 guard_async_result("goto_type_definition", async move {
1864 let uri = ¶ms.text_document_position_params.text_document.uri;
1865 let position = params.text_document_position_params.position;
1866 let source = self.get_open_text(uri).unwrap_or_default();
1867 let doc = match self.get_doc(uri) {
1868 Some(d) => d,
1869 None => return Ok(None),
1870 };
1871 let analysis = self.cached_analysis_async(uri).await;
1872 let open_docs = self.docs.docs_for(&self.open_urls());
1873 let docs = Arc::clone(&self.docs);
1874 let wi = self.workspace_index_async().await;
1875
1876 let response = self
1881 .blocking_gated(super::debug_gate::GATE_GOTO_TYPE_DEFINITION, move || {
1882 let mut results = goto_type_definition_exact(
1887 &source,
1888 &doc,
1889 analysis.as_deref(),
1890 &open_docs,
1891 position,
1892 );
1893 if results.is_empty() {
1894 let exact_uri = |fqn: &str| {
1895 docs.class_ref_by_fqn(&wi, fqn)
1896 .and_then(|cr| wi.at(cr).map(|(uri, _)| uri.clone()))
1897 };
1898 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1899 results = goto_type_definition_from_index_exact(
1900 &source,
1901 &doc,
1902 analysis.as_deref(),
1903 position,
1904 &exact_uri,
1905 &get_doc,
1906 );
1907 }
1908 if results.is_empty() {
1909 results = goto_type_definition_short_name_fallback(
1910 &source,
1911 &doc,
1912 analysis.as_deref(),
1913 &open_docs,
1914 position,
1915 );
1916 }
1917 if results.is_empty() {
1918 let class_candidate_uris = |short: &str| {
1919 docs.class_candidates_by_short_name(&wi, short)
1920 .into_iter()
1921 .filter_map(|cr| wi.at(cr).map(|(uri, _)| uri.clone()))
1922 .collect()
1923 };
1924 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
1925 results = goto_type_definition_from_index_short_name_fallback(
1926 &source,
1927 &doc,
1928 analysis.as_deref(),
1929 position,
1930 &class_candidate_uris,
1931 &get_doc,
1932 );
1933 }
1934
1935 match results.len() {
1937 0 => None,
1938 1 => Some(GotoDefinitionResponse::Scalar(
1939 results.into_iter().next().unwrap(),
1940 )),
1941 _ => Some(GotoDefinitionResponse::Array(results)),
1942 }
1943 })
1944 .await
1945 .unwrap_or_default();
1946 Ok(response)
1947 })
1948 .await
1949 }
1950
1951 async fn prepare_type_hierarchy(
1952 &self,
1953 params: TypeHierarchyPrepareParams,
1954 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1955 guard_async_result("prepare_type_hierarchy", async move {
1956 let uri = ¶ms.text_document_position_params.text_document.uri;
1957 let position = params.text_document_position_params.position;
1958 let source = self.get_open_text(uri).unwrap_or_default();
1959 let wi = self.workspace_index_async().await;
1961 let docs = Arc::clone(&self.docs);
1962 let uri = uri.clone();
1963 let item = self
1964 .blocking_gated(super::debug_gate::GATE_TYPE_HIERARCHY, move || {
1965 let class_candidates =
1966 |short: &str| docs.class_candidates_by_short_name(&wi, short);
1967 prepare_type_hierarchy_from_workspace(
1968 &source,
1969 &uri,
1970 &wi,
1971 position,
1972 &class_candidates,
1973 )
1974 })
1975 .await
1976 .flatten();
1977 Ok(item.map(|item| vec![item]))
1978 })
1979 .await
1980 }
1981
1982 async fn supertypes(
1983 &self,
1984 params: TypeHierarchySupertypesParams,
1985 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
1986 guard_async_result("supertypes", async move {
1987 let wi = self.workspace_index_async().await;
1991 let loaded_new = self
1992 .ensure_direct_supertypes_loaded(
1993 ¶ms.item.name,
1994 crate::navigation::type_hierarchy::item_fqn(¶ms.item),
1995 &wi,
1996 )
1997 .await;
1998 let wi = if loaded_new {
1999 self.workspace_index_async().await
2000 } else {
2001 wi
2002 };
2003 let docs = Arc::clone(&self.docs);
2004 let item = params.item;
2005 let result = self
2006 .blocking_gated(super::debug_gate::GATE_TYPE_HIERARCHY, move || {
2007 let class_candidates =
2008 |short: &str| docs.class_candidates_by_short_name(&wi, short);
2009 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
2010 let resolve_class_ref =
2011 |fqn: &str| docs.resolve_class_ref_by_fqn_or_short_name_fallback(&wi, fqn);
2012 supertypes_of_from_workspace(
2013 &item,
2014 &wi,
2015 &class_candidates,
2016 &get_doc,
2017 &resolve_class_ref,
2018 )
2019 })
2020 .await
2021 .unwrap_or_default();
2022 Ok(if result.is_empty() {
2023 None
2024 } else {
2025 Some(result)
2026 })
2027 })
2028 .await
2029 }
2030
2031 async fn subtypes(
2032 &self,
2033 params: TypeHierarchySubtypesParams,
2034 ) -> Result<Option<Vec<TypeHierarchyItem>>> {
2035 guard_async_result("subtypes", async move {
2036 let wi = self.workspace_index_async().await;
2037 let docs = Arc::clone(&self.docs);
2038 let item = params.item;
2039 let result = self
2040 .blocking_gated(super::debug_gate::GATE_TYPE_HIERARCHY, move || {
2041 let item_fqn = crate::navigation::type_hierarchy::item_fqn(&item)
2042 .map(str::to_string)
2043 .or_else(|| {
2044 let candidates = docs.class_candidates_by_short_name(&wi, &item.name);
2046 candidates
2047 .iter()
2048 .filter_map(|r| wi.at(*r))
2049 .find(|(u, _)| **u == item.uri)
2050 .or_else(|| candidates.first().and_then(|r| wi.at(*r)))
2051 .map(|(_, cls)| cls.fqn.as_ref().to_string())
2052 });
2053 let subtype_urls = item_fqn
2054 .as_deref()
2055 .map(|f| docs.class_subtype_urls(f))
2056 .unwrap_or_default();
2057 let mention_candidates =
2058 |name: &str| docs.declaration_candidate_files(&wi, name);
2059 let get_doc = |uri: &Uri| docs.get_doc_salsa(uri);
2060 subtypes_of_mir_backed(
2061 &item,
2062 item_fqn.as_deref(),
2063 &wi,
2064 &subtype_urls,
2065 &mention_candidates,
2066 &get_doc,
2067 )
2068 })
2069 .await
2070 .unwrap_or_default();
2071 Ok(if result.is_empty() {
2072 None
2073 } else {
2074 Some(result)
2075 })
2076 })
2077 .await
2078 }
2079
2080 async fn code_lens(&self, params: CodeLensParams) -> Result<Option<Vec<CodeLens>>> {
2081 guard_async_result("code_lens", async move {
2082 let uri = ¶ms.text_document.uri;
2083 let doc = match self.get_doc(uri) {
2084 Some(d) => d,
2085 None => return Ok(None),
2086 };
2087 let docs = Arc::clone(&self.docs);
2093 let docs_cancel = Arc::clone(&self.docs);
2094 let uri_owned = uri.clone();
2095 let imports = self.file_imports(uri);
2096 let lenses = self
2097 .blocking("code_lens", move || {
2098 let (_interactive, cancel_rev) = docs.settled_write_rev_guard();
2101 code_lenses(
2102 &uri_owned,
2103 &doc,
2104 &docs,
2105 &imports,
2106 Some(cancel_rev),
2107 move || docs_cancel.write_rev() != cancel_rev,
2108 )
2109 })
2110 .await
2111 .unwrap_or_default();
2112 Ok(if lenses.is_empty() {
2113 None
2114 } else {
2115 Some(lenses)
2116 })
2117 })
2118 .await
2119 }
2120
2121 async fn code_lens_resolve(&self, params: CodeLens) -> Result<CodeLens> {
2122 let fallback = params.clone();
2123 let resolved = guard_async("code_lens_resolve", async move {
2124 Some(params)
2126 })
2127 .await;
2128 Ok(resolved.unwrap_or(fallback))
2129 }
2130
2131 async fn document_link(&self, params: DocumentLinkParams) -> Result<Option<Vec<DocumentLink>>> {
2132 guard_async_result("document_link", async move {
2133 let uri = params.text_document.uri;
2134 let doc = match self.get_doc(&uri) {
2135 Some(d) => d,
2136 None => return Ok(None),
2137 };
2138 let laravel = self.laravel.load_full();
2139 let links = self
2142 .blocking_gated(super::debug_gate::GATE_DOCUMENT_LINK, move || {
2143 let mut links = document_links(&uri, &doc, doc.source());
2144 links.extend(crate::laravel::document_links(&doc, &laravel));
2145 links.extend(crate::laravel::blade::document_links(
2146 &uri,
2147 doc.source(),
2148 &laravel,
2149 ));
2150 links
2151 })
2152 .await
2153 .unwrap_or_default();
2154 Ok(if links.is_empty() { None } else { Some(links) })
2155 })
2156 .await
2157 }
2158
2159 async fn document_link_resolve(&self, params: DocumentLink) -> Result<DocumentLink> {
2160 let fallback = params.clone();
2161 let resolved = guard_async("document_link_resolve", async move {
2162 Some(params)
2164 })
2165 .await;
2166 Ok(resolved.unwrap_or(fallback))
2167 }
2168
2169 async fn formatting(&self, params: DocumentFormattingParams) -> Result<Option<Vec<TextEdit>>> {
2170 guard_async_result("formatting", async move {
2171 let uri = ¶ms.text_document.uri;
2172 let source = self.get_open_text(uri).unwrap_or_default();
2173 Ok(self
2176 .blocking("formatting", move || format_document(&source))
2177 .await
2178 .unwrap_or_default())
2179 })
2180 .await
2181 }
2182
2183 async fn range_formatting(
2184 &self,
2185 params: DocumentRangeFormattingParams,
2186 ) -> Result<Option<Vec<TextEdit>>> {
2187 guard_async_result("range_formatting", async move {
2188 let uri = ¶ms.text_document.uri;
2189 let source = self.get_open_text(uri).unwrap_or_default();
2190 let range = params.range;
2191 Ok(self
2192 .blocking("range_formatting", move || format_range(&source, range))
2193 .await
2194 .unwrap_or_default())
2195 })
2196 .await
2197 }
2198
2199 async fn on_type_formatting(
2200 &self,
2201 params: DocumentOnTypeFormattingParams,
2202 ) -> Result<Option<Vec<TextEdit>>> {
2203 guard_async_result("on_type_formatting", async move {
2204 let uri = ¶ms.text_document_position.text_document.uri;
2205 let source = self.get_open_text(uri).unwrap_or_default();
2206 let position = params.text_document_position.position;
2207 let edits = self
2212 .blocking_gated(super::debug_gate::GATE_ON_TYPE_FORMATTING, move || {
2213 on_type_format(&source, position, ¶ms.ch, ¶ms.options)
2214 })
2215 .await
2216 .unwrap_or_default();
2217 Ok(if edits.is_empty() { None } else { Some(edits) })
2218 })
2219 .await
2220 }
2221
2222 async fn execute_command(
2223 &self,
2224 params: ExecuteCommandParams,
2225 ) -> Result<Option<serde_json::Value>> {
2226 guard_async_result("execute_command", async move {
2227 match params.command.as_str() {
2228 "php-lsp.runTest" => {
2229 let file_uri = params
2231 .arguments
2232 .first()
2233 .and_then(|v| v.as_str())
2234 .and_then(|s| (s).parse::<Uri>().ok());
2235 let filter = params
2236 .arguments
2237 .get(1)
2238 .and_then(|v| v.as_str())
2239 .unwrap_or("")
2240 .to_string();
2241
2242 let root = self.root_paths.load().first().cloned();
2243 let client = self.client.clone();
2244
2245 tokio::spawn(async move {
2246 run_phpunit(&client, &filter, root.as_deref(), file_uri.as_ref()).await;
2247 });
2248
2249 Ok(None)
2250 }
2251 _ => Ok(None),
2252 }
2253 })
2254 .await
2255 }
2256
2257 async fn will_rename_files(&self, params: RenameFilesParams) -> Result<Option<WorkspaceEdit>> {
2258 guard_async_result("will_rename_files", async move {
2259 self.handle_will_rename_files(params).await
2260 })
2261 .await
2262 }
2263
2264 async fn did_rename_files(&self, params: RenameFilesParams) {
2265 guard_async("did_rename_files", async move {
2266 self.handle_did_rename_files(params).await
2267 })
2268 .await
2269 }
2270
2271 async fn will_create_files(&self, params: CreateFilesParams) -> Result<Option<WorkspaceEdit>> {
2272 guard_async_result("will_create_files", async move {
2273 self.handle_will_create_files(params).await
2274 })
2275 .await
2276 }
2277
2278 async fn did_create_files(&self, params: CreateFilesParams) {
2279 guard_async("did_create_files", async move {
2280 self.handle_did_create_files(params).await
2281 })
2282 .await
2283 }
2284
2285 async fn will_delete_files(&self, params: DeleteFilesParams) -> Result<Option<WorkspaceEdit>> {
2288 guard_async_result("will_delete_files", async move {
2289 self.handle_will_delete_files(params).await
2290 })
2291 .await
2292 }
2293
2294 async fn did_delete_files(&self, params: DeleteFilesParams) {
2295 guard_async("did_delete_files", async move {
2296 self.handle_did_delete_files(params).await
2297 })
2298 .await
2299 }
2300
2301 async fn moniker(&self, params: MonikerParams) -> Result<Option<Vec<Moniker>>> {
2302 guard_async_result("moniker", async move {
2303 let uri = ¶ms.text_document_position_params.text_document.uri;
2304 let position = params.text_document_position_params.position;
2305 let source = self.get_open_text(uri).unwrap_or_default();
2306 let doc = match self.get_doc(uri) {
2307 Some(d) => d,
2308 None => return Ok(None),
2309 };
2310 let imports = self.file_imports(uri);
2311 let moniker = self
2312 .blocking_gated(super::debug_gate::GATE_MONIKER, move || {
2313 moniker_at(&source, &doc, position, &imports)
2314 })
2315 .await
2316 .flatten();
2317 Ok(moniker.map(|m| vec![m]))
2318 })
2319 .await
2320 }
2321
2322 async fn inline_value(&self, params: InlineValueParams) -> Result<Option<Vec<InlineValue>>> {
2323 guard_async_result("inline_value", async move {
2324 let uri = ¶ms.text_document.uri;
2325 let source = self.get_open_text(uri).unwrap_or_default();
2326 let range = params.range;
2327 let values = self
2328 .blocking_gated(super::debug_gate::GATE_INLINE_VALUE, move || {
2329 inline_values_in_range(&source, range)
2330 })
2331 .await
2332 .unwrap_or_default();
2333 Ok(if values.is_empty() {
2334 None
2335 } else {
2336 Some(values)
2337 })
2338 })
2339 .await
2340 }
2341
2342 async fn diagnostic(
2343 &self,
2344 params: DocumentDiagnosticParams,
2345 ) -> Result<DocumentDiagnosticReportResult> {
2346 let report = guard_async("diagnostic", async move {
2347 Some(self.handle_diagnostic(params).await)
2348 })
2349 .await;
2350 match report {
2351 Some(r) => r,
2352 None => Ok(DocumentDiagnosticReportResult::Report(
2353 DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport::default()),
2354 )),
2355 }
2356 }
2357
2358 async fn workspace_diagnostic(
2359 &self,
2360 params: WorkspaceDiagnosticParams,
2361 ) -> Result<WorkspaceDiagnosticReportResult> {
2362 let report = guard_async("workspace_diagnostic", async move {
2363 Some(self.handle_workspace_diagnostic(params).await)
2364 })
2365 .await;
2366 match report {
2367 Some(r) => r,
2368 None => Ok(WorkspaceDiagnosticReportResult::Report(
2369 WorkspaceDiagnosticReport::default(),
2370 )),
2371 }
2372 }
2373
2374 async fn code_action(&self, params: CodeActionParams) -> Result<Option<CodeActionResponse>> {
2375 guard_async_result("code_action", async move {
2376 self.handle_code_action(params).await
2377 })
2378 .await
2379 }
2380
2381 async fn code_action_resolve(&self, item: CodeAction) -> Result<CodeAction> {
2382 guard_async_result("code_action_resolve", async move {
2383 self.handle_code_action_resolve(item).await
2384 })
2385 .await
2386 }
2387}
2388
2389fn subtype_site_to_location(file: &str, range: &mir_analyzer::Range) -> Option<Location> {
2392 let uri = (file).parse::<Uri>().ok()?;
2393 let line = range.start.line.saturating_sub(1);
2394 Some(Location {
2395 uri,
2396 range: tower_lsp_server::ls_types::Range {
2397 start: tower_lsp_server::ls_types::Position {
2398 line,
2399 character: range.start.column,
2400 },
2401 end: tower_lsp_server::ls_types::Position {
2402 line,
2403 character: range.end.column,
2404 },
2405 },
2406 })
2407}