Main completion handler — called by `LanguageServer::completion`. Tries each completion strategy in priority order and returns the first one that produces results. Falls back to no completions when nothing matches.
(
&self,
params: CompletionParams,
)
| 259 | /// first one that produces results. Falls back to no completions |
| 260 | /// when nothing matches. |
| 261 | pub(crate) async fn handle_completion( |
| 262 | &self, |
| 263 | params: CompletionParams, |
| 264 | ) -> Result<Option<CompletionResponse>> { |
| 265 | let uri = params.text_document_position.text_document.uri.to_string(); |
| 266 | let mut position = params.text_document_position.position; |
| 267 | |
| 268 | // Get file content for offset calculation. For Blade files, |
| 269 | // use the virtual PHP content and translate the cursor position |
| 270 | // so that variable resolution walks the preprocessed AST. |
| 271 | let content = if self.is_blade_file(&uri) { |
| 272 | let vc = self.blade_virtual_content.read(); |
| 273 | if let Some(virtual_php) = vc.get(&uri) { |
| 274 | position = self.translate_blade_to_php(&uri, position); |
| 275 | Some(virtual_php.clone()) |
| 276 | } else { |
| 277 | self.get_file_content(&uri) |
| 278 | } |
| 279 | } else { |
| 280 | self.get_file_content(&uri) |
| 281 | }; |
| 282 | |
| 283 | if let Some(content) = content { |
| 284 | // Activate the chain resolution cache so that shared chain |
| 285 | // prefixes are resolved once and reused within this completion |
| 286 | // request. The guard is re-entrant safe. |
| 287 | let _chain_guard = super::resolver::with_chain_resolution_cache(); |
| 288 | let _body_infer_guard = self.activate_body_return_inferrer(); |
| 289 | |
| 290 | // Gather per-file context (classes, use-map, namespace) in one |
| 291 | // call instead of three separate lock-and-unwrap blocks. |
| 292 | // Use the cursor offset for position-aware namespace resolution |
| 293 | // so that multi-namespace files resolve to the correct namespace. |
| 294 | let cursor_offset = crate::util::position_to_offset(&content, position); |
| 295 | let ctx = self.file_context_at(&uri, cursor_offset); |
| 296 | |
| 297 | // ── Suppress completion inside non-doc comments ───────── |
| 298 | if crate::completion::comment_position::is_inside_non_doc_comment(&content, position) { |
| 299 | return Ok(None); |
| 300 | } |
| 301 | |
| 302 | // ── PHPDoc block generation on `/**` ──────────────────── |
| 303 | // When the user types `/**` above a declaration, generate |
| 304 | // a complete docblock skeleton as a single snippet item. |
| 305 | // Must run before the docblock-interior checks below. |
| 306 | { |
| 307 | let class_loader = self.class_loader(&ctx); |
| 308 | let function_loader = self.function_loader(&ctx); |
| 309 | if let Some(response) = crate::completion::phpdoc::generation::try_generate_docblock( |
| 310 | &content, |
| 311 | position, |
| 312 | &ctx.use_map, |
| 313 | &ctx.namespace, |
| 314 | &ctx.classes, |
| 315 | &class_loader, |
| 316 | Some(&function_loader), |
| 317 | ) { |
| 318 | return Ok(Some(response)); |
no test coverage detected