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