@@ -16,6 +16,39 @@ use tower_lsp::lsp_types::*;
1616
1717use crate :: Backend ;
1818
19+ /// PHP scalar and built-in types offered in docblock type positions.
20+ ///
21+ /// These are prepended to class-name results so that typing `@param str`
22+ /// suggests `string` alongside any user-defined classes starting with `str`.
23+ const PHPDOC_SCALAR_TYPES : & [ & str ] = & [
24+ "string" ,
25+ "int" ,
26+ "float" ,
27+ "bool" ,
28+ "array" ,
29+ "object" ,
30+ "mixed" ,
31+ "void" ,
32+ "null" ,
33+ "callable" ,
34+ "iterable" ,
35+ "never" ,
36+ "self" ,
37+ "static" ,
38+ "parent" ,
39+ "true" ,
40+ "false" ,
41+ "resource" ,
42+ "class-string" ,
43+ "positive-int" ,
44+ "negative-int" ,
45+ "non-empty-string" ,
46+ "non-empty-array" ,
47+ "non-empty-list" ,
48+ "list" ,
49+ "numeric-string" ,
50+ ] ;
51+
1952impl Backend {
2053 /// Main completion handler — called by `LanguageServer::completion`.
2154 ///
@@ -46,6 +79,15 @@ impl Backend {
4679 if let Some ( content) = content {
4780 let classes = classes. unwrap_or_default ( ) ;
4881
82+ // ── Suppress completion inside non-doc comments ─────────
83+ // When the cursor is inside a `//` line comment or a `/* … */`
84+ // block comment (but NOT a `/** … */` docblock), return no
85+ // completions — typing inside comments should not trigger
86+ // suggestions.
87+ if crate :: completion:: phpdoc:: is_inside_non_doc_comment ( & content, position) {
88+ return Ok ( None ) ;
89+ }
90+
4991 // Gather the current file's `use` statement mappings and namespace
5092 // so the class_loader can resolve short names like `Resource` to
5193 // their fully-qualified equivalents like `Klarna\Rest\Resource`.
@@ -85,6 +127,98 @@ impl Backend {
85127 return Ok ( Some ( CompletionResponse :: Array ( items) ) ) ;
86128 }
87129
130+ // ── Docblock type / variable completion ─────────────────
131+ // When the cursor is inside a `/** … */` docblock at a
132+ // recognised tag position (e.g. after `@param `, `@return `,
133+ // `@throws `, `@var `, …), offer class-name or $variable
134+ // completions as appropriate. At all other docblock
135+ // positions (descriptions, unknown tags) suppress the
136+ // remaining strategies so random words don't trigger
137+ // class / variable suggestions.
138+ if crate :: completion:: phpdoc:: is_inside_docblock ( & content, position) {
139+ use crate :: completion:: phpdoc:: {
140+ DocblockTypingContext , detect_docblock_typing_position, extract_symbol_info,
141+ } ;
142+
143+ match detect_docblock_typing_position ( & content, position) {
144+ Some ( DocblockTypingContext :: Type { partial } ) => {
145+ // Offer scalar / built-in types first, then class
146+ // / interface / enum names from the project.
147+ let partial_lower = partial. to_lowercase ( ) ;
148+ let mut items: Vec < CompletionItem > = PHPDOC_SCALAR_TYPES
149+ . iter ( )
150+ . filter ( |t| t. to_lowercase ( ) . starts_with ( & partial_lower) )
151+ . enumerate ( )
152+ . map ( |( idx, t) | CompletionItem {
153+ label : t. to_string ( ) ,
154+ kind : Some ( CompletionItemKind :: KEYWORD ) ,
155+ detail : Some ( "PHP built-in type" . to_string ( ) ) ,
156+ insert_text : Some ( t. to_string ( ) ) ,
157+ filter_text : Some ( t. to_string ( ) ) ,
158+ sort_text : Some ( format ! ( "0_scalar_{:03}" , idx) ) ,
159+ ..CompletionItem :: default ( )
160+ } )
161+ . collect ( ) ;
162+
163+ let ( class_items, class_incomplete) = self . build_class_name_completions (
164+ & file_use_map,
165+ & file_namespace,
166+ & partial,
167+ & content,
168+ false , // not a `new` context
169+ ) ;
170+ items. extend ( class_items) ;
171+
172+ if !items. is_empty ( ) {
173+ return Ok ( Some ( CompletionResponse :: List ( CompletionList {
174+ is_incomplete : class_incomplete,
175+ items,
176+ } ) ) ) ;
177+ }
178+ return Ok ( None ) ;
179+ }
180+ Some ( DocblockTypingContext :: Variable { partial } ) => {
181+ // Offer $parameter names from the function declaration.
182+ let sym = extract_symbol_info ( & content, position) ;
183+ let partial_lower = partial. to_lowercase ( ) ;
184+ let items: Vec < CompletionItem > = sym
185+ . params
186+ . iter ( )
187+ . filter ( |( _, name) | {
188+ partial_lower. is_empty ( )
189+ || name. to_lowercase ( ) . starts_with ( & partial_lower)
190+ } )
191+ . map ( |( type_hint, name) | {
192+ let detail = type_hint. as_deref ( ) . unwrap_or ( "mixed" ) . to_string ( ) ;
193+ // Always use the full `$name` as insert_text
194+ // — the LSP client replaces the typed prefix
195+ // (whether `$`, `$na`, or empty) with whatever
196+ // we provide, matching how regular variable
197+ // completion works in variable_completion.rs.
198+ CompletionItem {
199+ label : name. clone ( ) ,
200+ kind : Some ( CompletionItemKind :: VARIABLE ) ,
201+ detail : Some ( detail) ,
202+ insert_text : Some ( name. clone ( ) ) ,
203+ filter_text : Some ( name. clone ( ) ) ,
204+ sort_text : Some ( format ! ( "0_{}" , name. to_lowercase( ) ) ) ,
205+ ..CompletionItem :: default ( )
206+ }
207+ } )
208+ . collect ( ) ;
209+ if !items. is_empty ( ) {
210+ return Ok ( Some ( CompletionResponse :: Array ( items) ) ) ;
211+ }
212+ return Ok ( None ) ;
213+ }
214+ None => {
215+ // Description text or unrecognised position — no
216+ // completions.
217+ return Ok ( None ) ;
218+ }
219+ }
220+ }
221+
88222 // ── Named argument completion ───────────────────────────
89223 // When the cursor is inside the parentheses of a function or
90224 // method call, offer parameter names as `name:` completions.
0 commit comments