@@ -48,13 +48,22 @@ impl Backend {
4848 /// - `foo|` → `None`
4949 pub fn extract_partial_variable_name ( content : & str , position : Position ) -> Option < String > {
5050 let lines: Vec < & str > = content. lines ( ) . collect ( ) ;
51- if position . line as usize >= lines. len ( ) {
51+ if lines. is_empty ( ) {
5252 return None ;
5353 }
5454
55- let line = lines[ position. line as usize ] ;
55+ // When the cursor is past the last line (editor can send this for
56+ // a trailing blank line after the final newline), treat it as the
57+ // end of the last line so variables defined earlier are still found.
58+ let ( line, col) = if position. line as usize >= lines. len ( ) {
59+ let last = lines[ lines. len ( ) - 1 ] ;
60+ ( last, last. chars ( ) . count ( ) )
61+ } else {
62+ let l = lines[ position. line as usize ] ;
63+ ( l, ( position. character as usize ) . min ( l. chars ( ) . count ( ) ) )
64+ } ;
65+
5666 let chars: Vec < char > = line. chars ( ) . collect ( ) ;
57- let col = ( position. character as usize ) . min ( chars. len ( ) ) ;
5867
5968 // Walk backwards through identifier characters
6069 let mut i = col;
@@ -181,7 +190,10 @@ impl Backend {
181190 // +1 for the newline character
182191 offset += line. len ( ) + 1 ;
183192 }
184- None
193+ // Line number is past the end of the file — treat the cursor as
194+ // being at the very end of the content so that all preceding
195+ // statements are still visible.
196+ Some ( content. len ( ) )
185197 }
186198}
187199
@@ -268,6 +280,20 @@ fn find_scope_and_collect<'b>(
268280 }
269281 }
270282
283+ // If the cursor is past the end of the last statement and that
284+ // statement is a namespace, the user is typing at EOF inside an
285+ // unbraced namespace (`namespace Foo;`). The parser's span for the
286+ // namespace may not extend to cover newly-typed content (e.g. a bare
287+ // `$`), so the range check above misses it. Recurse into the last
288+ // namespace so variables declared inside it are still visible.
289+ if let Some ( & Statement :: Namespace ( ns) ) = stmts. last ( ) {
290+ let ns_span = ns. span ( ) ;
291+ if cursor_offset > ns_span. end . offset {
292+ find_scope_and_collect ( ns. statements ( ) . iter ( ) , cursor_offset, vars) ;
293+ return ;
294+ }
295+ }
296+
271297 // Cursor is in top-level code — collect from all top-level statements.
272298 collect_from_statements ( stmts. into_iter ( ) , cursor_offset, vars) ;
273299}
@@ -375,8 +401,7 @@ fn collect_from_statements<'b>(
375401 // inside the foreach body. Outside the loop these
376402 // iteration variables should not be in scope.
377403 let body_span = foreach. body . span ( ) ;
378- if cursor_offset >= body_span. start . offset
379- && cursor_offset <= body_span. end . offset
404+ if cursor_offset >= body_span. start . offset && cursor_offset <= body_span. end . offset
380405 {
381406 if let Some ( key_expr) = foreach. target . key ( ) {
382407 collect_var_name_from_expression ( key_expr, vars) ;
0 commit comments