@@ -114,7 +114,7 @@ impl Backend {
114114 /// identifier (at the cursor) is `throw new` (with optional
115115 /// whitespace). This tells the handler to restrict completion to
116116 /// Throwable descendants only and skip constants / functions.
117- pub fn is_throw_new_context ( content : & str , position : Position ) -> bool {
117+ pub ( crate ) fn is_throw_new_context ( content : & str , position : Position ) -> bool {
118118 let lines: Vec < & str > = content. lines ( ) . collect ( ) ;
119119 if position. line as usize >= lines. len ( ) {
120120 return false ;
@@ -243,6 +243,8 @@ impl Backend {
243243 /// re-request as the user types more characters.
244244 const MAX_CLASS_COMPLETIONS : usize = 100 ;
245245
246+ /// Build completion items for class, interface, trait, and enum names
247+ /// matching `prefix`.
246248 pub ( crate ) fn build_class_name_completions (
247249 & self ,
248250 file_use_map : & HashMap < String , String > ,
@@ -506,38 +508,7 @@ impl Backend {
506508 }
507509
508510 // Look up ClassInfo from ast_map (no disk I/O).
509- let last_segment = short_name ( normalized) ;
510- let expected_ns: Option < & str > = if normalized. contains ( '\\' ) {
511- Some ( & normalized[ ..normalized. len ( ) - last_segment. len ( ) - 1 ] )
512- } else {
513- None
514- } ;
515-
516- let cls = {
517- let Some ( map) = self . ast_map . lock ( ) . ok ( ) else {
518- return false ;
519- } ;
520- let nmap = self . namespace_map . lock ( ) . ok ( ) ;
521- let mut found: Option < ClassInfo > = None ;
522- for ( uri, classes) in map. iter ( ) {
523- if let Some ( c) = classes. iter ( ) . find ( |c| c. name == last_segment) {
524- if let Some ( exp_ns) = expected_ns {
525- let file_ns = nmap
526- . as_ref ( )
527- . and_then ( |nm| nm. get ( uri) )
528- . and_then ( |opt| opt. as_deref ( ) ) ;
529- if file_ns != Some ( exp_ns) {
530- continue ;
531- }
532- }
533- found = Some ( c. clone ( ) ) ;
534- break ;
535- }
536- }
537- found
538- } ;
539-
540- match cls {
511+ match self . find_class_in_ast_map ( class_name) {
541512 Some ( ci) => match & ci. parent_class {
542513 Some ( parent) => self . is_throwable_descendant ( parent, depth + 1 ) ,
543514 None => false , // no parent, not a Throwable type
@@ -557,34 +528,10 @@ impl Backend {
557528 /// interface, trait, enum, or abstract class. This never triggers
558529 /// disk I/O.
559530 fn is_instantiable_or_unloaded ( & self , class_name : & str ) -> bool {
560- let normalized = class_name. strip_prefix ( '\\' ) . unwrap_or ( class_name) ;
561- let last_segment = short_name ( normalized) ;
562- let expected_ns: Option < & str > = if normalized. contains ( '\\' ) {
563- Some ( & normalized[ ..normalized. len ( ) - last_segment. len ( ) - 1 ] )
564- } else {
565- None
566- } ;
567-
568- let Some ( map) = self . ast_map . lock ( ) . ok ( ) else {
569- return true ;
570- } ;
571- let nmap = self . namespace_map . lock ( ) . ok ( ) ;
572- for ( uri, classes) in map. iter ( ) {
573- if let Some ( c) = classes. iter ( ) . find ( |c| c. name == last_segment) {
574- if let Some ( exp_ns) = expected_ns {
575- let file_ns = nmap
576- . as_ref ( )
577- . and_then ( |nm| nm. get ( uri) )
578- . and_then ( |opt| opt. as_deref ( ) ) ;
579- if file_ns != Some ( exp_ns) {
580- continue ;
581- }
582- }
583- return c. kind == ClassLikeKind :: Class && !c. is_abstract ;
584- }
531+ match self . find_class_in_ast_map ( class_name) {
532+ Some ( c) => c. kind == ClassLikeKind :: Class && !c. is_abstract ,
533+ None => true , // not found in ast_map — unloaded, so allow it
585534 }
586- // Not found in ast_map — unloaded, so allow it.
587- true
588535 }
589536
590537 /// Check whether the class identified by `class_name` is a concrete,
@@ -595,33 +542,8 @@ impl Backend {
595542 /// and classes that are not currently loaded. This never triggers
596543 /// disk I/O.
597544 fn is_concrete_class_in_ast_map ( & self , class_name : & str ) -> bool {
598- let normalized = class_name. strip_prefix ( '\\' ) . unwrap_or ( class_name) ;
599- let last_segment = short_name ( normalized) ;
600- let expected_ns: Option < & str > = if normalized. contains ( '\\' ) {
601- Some ( & normalized[ ..normalized. len ( ) - last_segment. len ( ) - 1 ] )
602- } else {
603- None
604- } ;
605-
606- let Some ( map) = self . ast_map . lock ( ) . ok ( ) else {
607- return false ;
608- } ;
609- let nmap = self . namespace_map . lock ( ) . ok ( ) ;
610- for ( uri, classes) in map. iter ( ) {
611- if let Some ( c) = classes. iter ( ) . find ( |c| c. name == last_segment) {
612- if let Some ( exp_ns) = expected_ns {
613- let file_ns = nmap
614- . as_ref ( )
615- . and_then ( |nm| nm. get ( uri) )
616- . and_then ( |opt| opt. as_deref ( ) ) ;
617- if file_ns != Some ( exp_ns) {
618- continue ;
619- }
620- }
621- return c. kind == ClassLikeKind :: Class && !c. is_abstract ;
622- }
623- }
624- false
545+ self . find_class_in_ast_map ( class_name)
546+ . is_some_and ( |c| c. kind == ClassLikeKind :: Class && !c. is_abstract )
625547 }
626548
627549 /// Collect the FQN of every class that is currently loaded in the
@@ -923,6 +845,7 @@ impl Backend {
923845 /// is truncated and `is_incomplete` is `true`.
924846 const MAX_CONSTANT_COMPLETIONS : usize = 100 ;
925847
848+ /// Build completion items for global constants matching `prefix`.
926849 pub ( crate ) fn build_constant_completions ( & self , prefix : & str ) -> ( Vec < CompletionItem > , bool ) {
927850 let prefix_lower = prefix. strip_prefix ( '\\' ) . unwrap_or ( prefix) . to_lowercase ( ) ;
928851 let mut seen: HashSet < String > = HashSet :: new ( ) ;
@@ -1032,6 +955,7 @@ impl Backend {
1032955 /// is truncated and `is_incomplete` is `true`.
1033956 const MAX_FUNCTION_COMPLETIONS : usize = 100 ;
1034957
958+ /// Build completion items for global functions matching `prefix`.
1035959 pub ( crate ) fn build_function_completions ( & self , prefix : & str ) -> ( Vec < CompletionItem > , bool ) {
1036960 let prefix_lower = prefix. strip_prefix ( '\\' ) . unwrap_or ( prefix) . to_lowercase ( ) ;
1037961 let mut seen: HashSet < String > = HashSet :: new ( ) ;
0 commit comments