Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

variables improvements #199

Merged
merged 10 commits into from
Jan 8, 2025
8 changes: 8 additions & 0 deletions src/adapters/DebugProtocolAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,8 @@ export class DebugProtocolAdapter {
let evaluateName: string;
if (!parentEvaluateName?.trim()) {
evaluateName = name;
} else if (variable.isVirtual) {
evaluateName = `${parentEvaluateName}.${name}`;
} else if (typeof name === 'string') {
evaluateName = `${parentEvaluateName}["${name}"]`;
} else if (typeof name === 'number') {
Expand Down Expand Up @@ -691,6 +693,12 @@ export class DebugProtocolAdapter {
container.children.push(childContainer);
}
}

//show virtual variables in the UI
if (variable.isVirtual) {
container.presentationHint = 'virtual';
}

return container;
}

Expand Down
10 changes: 5 additions & 5 deletions src/adapters/TelnetAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ export class TelnetAdapter {
//the array/associative array print is a loop of every value, so handle that
children = this.getForLoopPrintedChildren(expression, data);
children.push({
name: '[[count]]',
name: '$count',
value: children.length.toString(),
type: 'integer',
highLevelType: HighLevelType.primative,
Expand Down Expand Up @@ -651,7 +651,7 @@ export class TelnetAdapter {
//add a computed `[[children]]` property to allow expansion of node children
if (lowerExpressionType === 'rosgnode') {
let nodeChildren = <EvaluateContainer>{
name: '[[children]]',
name: '$children',
type: 'roArray',
highLevelType: 'array',
presentationHint: 'virtual',
Expand All @@ -666,12 +666,12 @@ export class TelnetAdapter {
children.push({
//look up the name of the xml element
...await this.getVariable(`${expression}.GetName()`),
name: '[[name]]',
name: '$name',
presentationHint: 'virtual'
});

children.push({
name: '[[attributes]]',
name: '$attributes',
type: 'roAssociativeArray',
highLevelType: HighLevelType.array,
evaluateName: `${expression}.GetAttributes()`,
Expand All @@ -681,7 +681,7 @@ export class TelnetAdapter {

//add a computed `[[children]]` property to allow expansion of child elements
children.push({
name: '[[children]]',
name: '$children',
type: 'roArray',
highLevelType: HighLevelType.array,
evaluateName: `${expression}.GetChildNodes()`,
Expand Down
10 changes: 10 additions & 0 deletions src/debugProtocol/client/DebugProtocolClient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,7 @@ describe('DebugProtocolClient', () => {
childCount: 0,
isConst: false,
isContainer: false,
isVirtual: false,
refCount: 0
} as Variable);
});
Expand Down Expand Up @@ -720,6 +721,7 @@ describe('DebugProtocolClient', () => {
childCount: 0,
isConst: false,
isContainer: false,
isVirtual: false,
refCount: 0
} as Variable);
});
Expand Down Expand Up @@ -1004,14 +1006,18 @@ describe('DebugProtocolClient', () => {
requestId: 1,
command: Command.Variables,
enableForceCaseInsensitivity: false,
getVirtualKeys: false,
includesVirtualPath: false,
getChildKeys: true,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [{
name: 'm',
isVirtual: false,
forceCaseInsensitive: false
}, {
name: 'top',
isVirtual: false,
forceCaseInsensitive: false
}]
} as VariablesRequest['data']);
Expand All @@ -1032,14 +1038,18 @@ describe('DebugProtocolClient', () => {
requestId: 2,
command: Command.Variables,
enableForceCaseInsensitivity: true,
getVirtualKeys: false,
includesVirtualPath: false,
getChildKeys: true,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [{
name: 'm',
isVirtual: false,
forceCaseInsensitive: true
}, {
name: 'top',
isVirtual: false,
forceCaseInsensitive: false
}]
} as VariablesRequest['data']);
Expand Down
9 changes: 8 additions & 1 deletion src/debugProtocol/client/DebugProtocolClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ export class DebugProtocolClient {
return semver.satisfies(this.protocolVersion, '>=3.2.0');
}

public get supportsVirtualVariables() {
return semver.satisfies(this.protocolVersion, '>=3.3.0');
}

/**
* Get a promise that resolves after an event occurs exactly once
*/
Expand Down Expand Up @@ -474,10 +478,13 @@ export class DebugProtocolClient {
threadIndex: threadIndex,
stackFrameIndex: stackFrameIndex,
getChildKeys: true,
getVirtualKeys: this.supportsVirtualVariables,
variablePathEntries: variablePathEntries.map(x => ({
//remove leading and trailing quotes
name: x.replace(/^"/, '').replace(/"$/, ''),
forceCaseInsensitive: !x.startsWith('"') && !x.endsWith('"')
forceCaseInsensitive: !x.startsWith('"') && !x.endsWith('"'),
//vars that start with `'$'` are virtual (AA keys will wrapped in quotes so would start with `"$`
isVirtual: x.startsWith('$') // || x.startsWith('"$')
})),
//starting in protocol v3.1.0, it supports marking certain path items as case-insensitive (i.e. parts of DottedGet expressions)
enableForceCaseInsensitivity: semver.satisfies(this.protocolVersion, '>=3.1.0') && variablePathEntries.length > 0
Expand Down
122 changes: 104 additions & 18 deletions src/debugProtocol/events/requests/VariablesRequest.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ describe('VariablesRequest', () => {
requestId: 3,
getChildKeys: true,
enableForceCaseInsensitivity: false,
getVirtualKeys: false,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: true },
{ name: 'b', forceCaseInsensitive: true },
{ name: 'c', forceCaseInsensitive: true }
{ name: 'a', forceCaseInsensitive: true, isVirtual: false },
{ name: 'b', forceCaseInsensitive: true, isVirtual: false },
{ name: 'c', forceCaseInsensitive: true, isVirtual: false }
]
});

Expand All @@ -24,12 +25,14 @@ describe('VariablesRequest', () => {

getChildKeys: true,
enableForceCaseInsensitivity: false,
getVirtualKeys: false,
includesVirtualPath: false,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: false },
{ name: 'b', forceCaseInsensitive: false },
{ name: 'c', forceCaseInsensitive: false }
{ name: 'a', forceCaseInsensitive: false, isVirtual: false },
{ name: 'b', forceCaseInsensitive: false, isVirtual: false },
{ name: 'c', forceCaseInsensitive: false, isVirtual: false }
]
});

Expand All @@ -43,13 +46,15 @@ describe('VariablesRequest', () => {
//variable_request_flags // 1 byte
getChildKeys: true, // 0 bytes
enableForceCaseInsensitivity: false, // 0 bytes
getVirtualKeys: false,
includesVirtualPath: false,
stackFrameIndex: 1, // 4 bytes
threadIndex: 2, // 4 bytes
// variable_path_len // 4 bytes
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: false }, // 2 bytes
{ name: 'b', forceCaseInsensitive: false }, // 2 bytes
{ name: 'c', forceCaseInsensitive: false } // 2 bytes
{ name: 'a', forceCaseInsensitive: false, isVirtual: false }, // 2 bytes
{ name: 'b', forceCaseInsensitive: false, isVirtual: false }, // 2 bytes
{ name: 'c', forceCaseInsensitive: false, isVirtual: false } // 2 bytes
]
});
});
Expand All @@ -59,12 +64,13 @@ describe('VariablesRequest', () => {
requestId: 3,
getChildKeys: false,
enableForceCaseInsensitivity: true,
getVirtualKeys: false,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: true },
{ name: 'b', forceCaseInsensitive: false },
{ name: 'c', forceCaseInsensitive: true }
{ name: 'a', forceCaseInsensitive: true, isVirtual: false },
{ name: 'b', forceCaseInsensitive: false, isVirtual: false },
{ name: 'c', forceCaseInsensitive: true, isVirtual: false }
]
});

Expand All @@ -75,12 +81,14 @@ describe('VariablesRequest', () => {

getChildKeys: false,
enableForceCaseInsensitivity: true,
getVirtualKeys: false,
includesVirtualPath: false,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: true },
{ name: 'b', forceCaseInsensitive: false },
{ name: 'c', forceCaseInsensitive: true }
{ name: 'a', forceCaseInsensitive: true, isVirtual: false },
{ name: 'b', forceCaseInsensitive: false, isVirtual: false },
{ name: 'c', forceCaseInsensitive: true, isVirtual: false }
]
});

Expand All @@ -94,21 +102,94 @@ describe('VariablesRequest', () => {
//variable_request_flags // 1 byte
getChildKeys: false, // 0 bytes
enableForceCaseInsensitivity: true, // 0 bytes
getVirtualKeys: false,
includesVirtualPath: false,
stackFrameIndex: 1, // 4 bytes
threadIndex: 2, // 4 bytes
// variable_path_len // 4 bytes
variablePathEntries: [
{
name: 'a', // 2 bytes
forceCaseInsensitive: true // 1 byte
forceCaseInsensitive: true, // 1 byte
isVirtual: false // 0 byte
}, // ?
{
name: 'b', // 2 bytes
forceCaseInsensitive: false // 1 byte
forceCaseInsensitive: false, // 1 byte
isVirtual: false // 0 byte
}, // ?
{
name: 'c', // 2 bytes
forceCaseInsensitive: true // 1 byte
forceCaseInsensitive: true, // 1 byte
isVirtual: false // 0 byte
} // ?
]
});
});

it('serializes and deserializes properly for case isVirtual lookups', () => {
const command = VariablesRequest.fromJson({
requestId: 3,
getChildKeys: false,
enableForceCaseInsensitivity: true,
getVirtualKeys: true,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: true, isVirtual: true },
{ name: 'b', forceCaseInsensitive: false, isVirtual: false },
{ name: 'c', forceCaseInsensitive: true, isVirtual: true }
]
});

expect(command.data).to.eql({
packetLength: undefined,
requestId: 3,
command: Command.Variables,

getChildKeys: false,
enableForceCaseInsensitivity: true,
getVirtualKeys: true,
includesVirtualPath: true,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: [
{ name: 'a', forceCaseInsensitive: true, isVirtual: true },
{ name: 'b', forceCaseInsensitive: false, isVirtual: false },
{ name: 'c', forceCaseInsensitive: true, isVirtual: true }
]
});

expect(
VariablesRequest.fromBuffer(command.toBuffer()).data
).to.eql({
packetLength: 37, // 4 bytes
requestId: 3, // 4 bytes
command: Command.Variables, // 4 bytes,

//variable_request_flags // 1 byte
getChildKeys: false, // 0 bytes
enableForceCaseInsensitivity: true, // 0 bytes
getVirtualKeys: true, // 0 bytes
includesVirtualPath: true, // 0 bytes
stackFrameIndex: 1, // 4 bytes
threadIndex: 2, // 4 bytes
// variable_path_len // 4 bytes
variablePathEntries: [
{
name: 'a', // 2 bytes
forceCaseInsensitive: true, // 1 byte
isVirtual: true // 1 byte
}, // ?
{
name: 'b', // 2 bytes
forceCaseInsensitive: false, // 1 byte
isVirtual: false // 1 byte
}, // ?
{
name: 'c', // 2 bytes
forceCaseInsensitive: true, // 1 byte
isVirtual: true // 1 byte
} // ?
]
});
Expand All @@ -119,6 +200,7 @@ describe('VariablesRequest', () => {
requestId: 3,
getChildKeys: false,
enableForceCaseInsensitivity: true,
getVirtualKeys: false,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: []
Expand All @@ -131,6 +213,8 @@ describe('VariablesRequest', () => {

getChildKeys: false,
enableForceCaseInsensitivity: true,
getVirtualKeys: false,
includesVirtualPath: false,
stackFrameIndex: 1,
threadIndex: 2,
variablePathEntries: []
Expand All @@ -146,6 +230,8 @@ describe('VariablesRequest', () => {
//variable_request_flags // 1 byte
getChildKeys: false, // 0 bytes
enableForceCaseInsensitivity: true, // 0 bytes
getVirtualKeys: false,
includesVirtualPath: false,
stackFrameIndex: 1, // 4 bytes
threadIndex: 2, // 4 bytes
// variable_path_len // 4 bytes
Expand Down
Loading