@@ -7,6 +7,7 @@ use tower_lsp::lsp_types::*;
77
88use crate :: Backend ;
99use crate :: types:: * ;
10+ use crate :: util:: skip_balanced_parens_back;
1011
1112impl Backend {
1213 /// Detect the access operator before the cursor position and extract
@@ -62,7 +63,11 @@ impl Backend {
6263 /// `chars[arrow_pos]` = `-`, `chars[arrow_pos+1]` = `>`).
6364 ///
6465 /// Handles:
65- /// `$this->`, `$var->`, `$this->prop->` (one level of chaining).
66+ /// - `$this->`, `$var->` (simple variable)
67+ /// - `$this->prop->` (property chain)
68+ /// - `app()->` (function call)
69+ /// - `$this->getService()->` (method call chain)
70+ /// - `ClassName::make()->` (static method call)
6671 fn extract_arrow_subject ( chars : & [ char ] , arrow_pos : usize ) -> String {
6772 // Position just before the `->`
6873 let end = arrow_pos;
@@ -73,6 +78,15 @@ impl Backend {
7378 i -= 1 ;
7479 }
7580
81+ // ── Function / method call: detect `)` before the operator ──
82+ // e.g. `app()->`, `$this->getService()->`, `Class::make()->`
83+ if i > 0
84+ && chars[ i - 1 ] == ')'
85+ && let Some ( call_subject) = Self :: extract_call_subject ( chars, i)
86+ {
87+ return call_subject;
88+ }
89+
7690 // Try to read an identifier (property name if chained)
7791 let ident_end = i;
7892 while i > 0 && ( chars[ i - 1 ] . is_alphanumeric ( ) || chars[ i - 1 ] == '_' ) {
@@ -105,6 +119,82 @@ impl Backend {
105119 Self :: extract_simple_variable ( chars, end)
106120 }
107121
122+ /// Extract the full call-expression subject when `)` appears before an
123+ /// operator.
124+ ///
125+ /// `paren_end` is the position one past the closing `)`.
126+ ///
127+ /// Returns subjects such as:
128+ /// - `"app()"` for a standalone function call
129+ /// - `"$this->getService()"` for an instance method call
130+ /// - `"ClassName::make()"` for a static method call
131+ fn extract_call_subject ( chars : & [ char ] , paren_end : usize ) -> Option < String > {
132+ let open = skip_balanced_parens_back ( chars, paren_end) ?;
133+ if open == 0 {
134+ return None ;
135+ }
136+
137+ // Read the function / method name before `(`
138+ let mut i = open;
139+ while i > 0 && ( chars[ i - 1 ] . is_alphanumeric ( ) || chars[ i - 1 ] == '_' ) {
140+ i -= 1 ;
141+ }
142+ if i == open {
143+ // No identifier before `(` — can't resolve
144+ return None ;
145+ }
146+ let func_name: String = chars[ i..open] . iter ( ) . collect ( ) ;
147+
148+ // Check what precedes the function name to determine the kind of
149+ // call expression.
150+
151+ // Instance method call: `$this->method()` / `$var->method()`
152+ if i >= 2 && chars[ i - 2 ] == '-' && chars[ i - 1 ] == '>' {
153+ let inner_subject = Self :: extract_simple_variable ( chars, i - 2 ) ;
154+ if !inner_subject. is_empty ( ) {
155+ return Some ( format ! ( "{}->{}()" , inner_subject, func_name) ) ;
156+ }
157+ }
158+
159+ // Null-safe method call: `$var?->method()`
160+ if i >= 3 && chars[ i - 3 ] == '?' && chars[ i - 2 ] == '-' && chars[ i - 1 ] == '>' {
161+ let inner_subject = Self :: extract_simple_variable ( chars, i - 3 ) ;
162+ if !inner_subject. is_empty ( ) {
163+ return Some ( format ! ( "{}?->{}()" , inner_subject, func_name) ) ;
164+ }
165+ }
166+
167+ // Static method call: `ClassName::method()` / `self::method()`
168+ if i >= 2 && chars[ i - 2 ] == ':' && chars[ i - 1 ] == ':' {
169+ let class_subject = Self :: extract_double_colon_subject_raw ( chars, i - 2 ) ;
170+ if !class_subject. is_empty ( ) {
171+ return Some ( format ! ( "{}::{}()" , class_subject, func_name) ) ;
172+ }
173+ }
174+
175+ // Standalone function call: `app()`
176+ Some ( format ! ( "{}()" , func_name) )
177+ }
178+
179+ /// Raw helper: extract identifier/keyword before `::` without going
180+ /// through the public `extract_double_colon_subject` API.
181+ fn extract_double_colon_subject_raw ( chars : & [ char ] , colon_pos : usize ) -> String {
182+ let mut i = colon_pos;
183+ while i > 0 && chars[ i - 1 ] == ' ' {
184+ i -= 1 ;
185+ }
186+ let end = i;
187+ while i > 0
188+ && ( chars[ i - 1 ] . is_alphanumeric ( ) || chars[ i - 1 ] == '_' || chars[ i - 1 ] == '\\' )
189+ {
190+ i -= 1 ;
191+ }
192+ if i > 0 && chars[ i - 1 ] == '$' {
193+ i -= 1 ;
194+ }
195+ chars[ i..end] . iter ( ) . collect ( )
196+ }
197+
108198 /// Extract a simple `$variable` ending at position `end` (exclusive).
109199 fn extract_simple_variable ( chars : & [ char ] , end : usize ) -> String {
110200 let mut i = end;
0 commit comments