Skip to content

Commit 98bd782

Browse files
felixfbeckerzobo
authored andcommitted
Add support for completionRequest
1 parent aae86d5 commit 98bd782

File tree

3 files changed

+109
-0
lines changed

3 files changed

+109
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Options specific to CLI debugging:
114114
- Stack traces, scope variables, superglobals, user defined constants
115115
- Arrays & objects (including classname, private and static properties)
116116
- Debug console
117+
- Autocompletion in debug console for variables, array indexes, object properties (even nested)
117118
- Watches
118119
- Run as CLI
119120
- Run without debugging

src/phpDebug.ts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -993,6 +993,106 @@ class PhpDebugSession extends vscode.DebugSession {
993993
this.sendResponse(response)
994994
}
995995

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+
9961096
protected async continueRequest(
9971097
response: VSCodeDebugProtocol.ContinueResponse,
9981098
args: VSCodeDebugProtocol.ContinueArguments

src/test/adapter.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,14 @@ describe('PHP Debug Adapter', () => {
678678
it('should return variable references for structured results')
679679
})
680680

681+
describe('completion', () => {
682+
it('should provide completion for local variables');
683+
it('should provide completion for superglobals');
684+
it('should provide completion for object properties');
685+
it('should provide completion for numeric array indexes');
686+
it('should provide completion for string array indexes');
687+
});
688+
681689
describe.skip('output events', () => {
682690
const program = path.join(TEST_PROJECT, 'output.php')
683691

0 commit comments

Comments
 (0)