@@ -877,7 +877,48 @@ impl Backend {
877877 ) ;
878878
879879 match member_items {
880- Some ( all_items) if !all_items. is_empty ( ) => Some ( CompletionResponse :: Array ( all_items) ) ,
880+ Some ( all_items) if !all_items. is_empty ( ) => {
881+ // ── B15: suppress snippet parentheses when `(` already follows ──
882+ // When completing `$obj->|()` or `$obj->meth|()`, the
883+ // character after the cursor (past any partial identifier)
884+ // is already `(`. Inserting a snippet with its own `()`
885+ // would produce `method()()`. Detect this and downgrade
886+ // callable snippets to plain method-name insertions.
887+ let paren_follows = {
888+ let byte_off =
889+ crate :: util:: position_to_byte_offset ( content, position) ;
890+ let rest = & content[ byte_off..] ;
891+ // Skip past any partial identifier the user has typed
892+ // (ASCII letters, digits, underscore).
893+ let after_ident = rest. trim_start_matches ( |c : char | {
894+ c. is_ascii_alphanumeric ( ) || c == '_'
895+ } ) ;
896+ after_ident. starts_with ( '(' )
897+ } ;
898+
899+ if paren_follows {
900+ let stripped: Vec < CompletionItem > = all_items
901+ . into_iter ( )
902+ . map ( |mut item| {
903+ if item. kind == Some ( CompletionItemKind :: METHOD )
904+ && item. insert_text_format
905+ == Some ( InsertTextFormat :: SNIPPET )
906+ {
907+ // Replace the snippet with just the method
908+ // name (the filter_text already holds it).
909+ if let Some ( ref name) = item. filter_text {
910+ item. insert_text = Some ( name. clone ( ) ) ;
911+ }
912+ item. insert_text_format = None ;
913+ }
914+ item
915+ } )
916+ . collect ( ) ;
917+ Some ( CompletionResponse :: Array ( stripped) )
918+ } else {
919+ Some ( CompletionResponse :: Array ( all_items) )
920+ }
921+ }
881922 _ => None ,
882923 }
883924 }
0 commit comments