@@ -993,6 +993,106 @@ class PhpDebugSession extends vscode.DebugSession {
993
993
this . sendResponse ( response )
994
994
}
995
995
996
+ protected async completionsRequest ( response : VSCodeDebugProtocol . CompletionsResponse , args : VSCodeDebugProtocol . CompletionsArguments ) {
997
+ try {
998
+ if ( ! args . frameId ) {
999
+ throw new Error ( 'No stack frame given' ) ;
1000
+ }
1001
+ const lineIndex : number = args . line ? args . line - 1 : 0 ;
1002
+ const lines : string [ ] = args . text . split ( '\n' ) ;
1003
+ /** The text before the cursor */
1004
+ const typed : string = [ ...lines . slice ( 0 , Math . max ( lineIndex - 1 , 0 ) ) , lines [ lineIndex ] . substring ( 0 , args . column ) ] . join ( '\n' ) ;
1005
+ let i = typed . length ;
1006
+ let containerName : string ;
1007
+ let operator : string | undefined ;
1008
+ let query : string ;
1009
+ while ( true ) {
1010
+ const substr = typed . substring ( 0 , i ) ;
1011
+ if ( / \[ $ / . test ( substr ) ) {
1012
+ // Numeric array index
1013
+ operator = '[' ;
1014
+ } else if ( / \[ ' $ / . test ( substr ) ) {
1015
+ // String array index
1016
+ operator = `['` ;
1017
+ } else if ( / - > $ / . test ( substr ) ) {
1018
+ operator = '->' ;
1019
+ } else if ( i > 0 ) {
1020
+ i -- ;
1021
+ continue ;
1022
+ }
1023
+ query = typed . substr ( i ) . toLowerCase ( ) ;
1024
+ containerName = typed . substring ( 0 , operator ? i - operator . length : i ) ;
1025
+ break ;
1026
+ }
1027
+ const frame = this . _stackFrames . get ( args . frameId ) ;
1028
+ const contexts = await frame . getContexts ( ) ;
1029
+ const targets : VSCodeDebugProtocol . CompletionItem [ ] = [ ] ;
1030
+ if ( ! containerName || ! operator ) {
1031
+ const responses = await Promise . all ( contexts . map ( context => context . getProperties ( ) ) ) ;
1032
+ for ( const properties of responses ) {
1033
+ for ( const property of properties ) {
1034
+ if ( property . name . toLowerCase ( ) . startsWith ( query ) ) {
1035
+ const text = property . name [ 0 ] === '$' ? property . name . substr ( 1 ) : property . name ;
1036
+ targets . push ( { label : property . name , text, type : 'variable' , start : i , length : property . name . length } ) ;
1037
+ }
1038
+ }
1039
+ }
1040
+ } else {
1041
+ // Search all contexts
1042
+ for ( const context of contexts ) {
1043
+ let response : xdebug . PropertyGetResponse | undefined ;
1044
+ try {
1045
+ response = await frame . connection . sendPropertyGetCommand ( { context, fullName : containerName } ) ;
1046
+ } catch ( err ) {
1047
+ // ignore
1048
+ }
1049
+ if ( response ) {
1050
+ for ( const property of response . children ) {
1051
+ if ( property . name . toLowerCase ( ) . startsWith ( query ) ) {
1052
+ let type : VSCodeDebugProtocol . CompletionItemType | undefined ;
1053
+ let text : string = property . name ;
1054
+ if ( operator === '->' ) {
1055
+ // Object
1056
+ type = 'property' ;
1057
+ } else if ( operator [ 0 ] === '[' ) {
1058
+ // Array
1059
+ if ( parseInt ( property . name ) + '' === property . name ) {
1060
+ // Numeric index
1061
+ if ( operator [ 1 ] === `'` ) {
1062
+ continue ;
1063
+ }
1064
+ type = 'value' ;
1065
+ text += ']' ;
1066
+ } else {
1067
+ // String index
1068
+ if ( operator [ 1 ] !== `'` ) {
1069
+ if ( query ) {
1070
+ continue ;
1071
+ } else {
1072
+ text = `'` + text ;
1073
+ }
1074
+ }
1075
+ type = 'text' ;
1076
+ text += `']` ;
1077
+ }
1078
+ }
1079
+ targets . push ( { label : property . name , text, type, start : i , length : property . name . length } ) ;
1080
+ }
1081
+ }
1082
+ // If we found the variable in one context (typically Locals), abort
1083
+ break ;
1084
+ }
1085
+ }
1086
+ }
1087
+ response . body = { targets} ;
1088
+ } catch ( err ) {
1089
+ this . sendErrorResponse ( response , err ) ;
1090
+ return ;
1091
+ }
1092
+ this . sendResponse ( response ) ;
1093
+ }
1094
+
1095
+
996
1096
protected async continueRequest (
997
1097
response : VSCodeDebugProtocol . ContinueResponse ,
998
1098
args : VSCodeDebugProtocol . ContinueArguments
0 commit comments