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

Add children roSGNode containers #192

Merged
merged 24 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b6d6dd9
Add children to the children of roSGNode containers. Make the evaluat…
Christian-Holbrook Jun 13, 2024
cbbc525
Fix lint issues
Christian-Holbrook Jun 13, 2024
ef39552
Move lint fixes
Christian-Holbrook Jun 13, 2024
8987a5f
Rename evalutTemporaryVariables to evaluateExpressionToTempVar
Christian-Holbrook Jun 14, 2024
d5d932b
Add the [[count]] value to the variables debug panel
Christian-Holbrook Jun 14, 2024
21ba1a1
Fix unit test
Christian-Holbrook Jun 26, 2024
cda34ee
Change counts type to "number" and make it greyed out with the "virtu…
Christian-Holbrook Jul 1, 2024
76c2a0c
Allow name to be a string or number when creating containers
Christian-Holbrook Jul 2, 2024
e6e2669
Set the [[count]] to zero if there are no elements and the type is "A…
Christian-Holbrook Jul 3, 2024
06d6416
Move logic for adding new variables into a helper file / function
Christian-Holbrook Nov 20, 2024
033e160
Merge branch 'master' into evaluate-roSGNode-Children
Christian-Holbrook Nov 20, 2024
eb0d1de
Return the container object
Christian-Holbrook Nov 21, 2024
8e7c450
Change the gate keeping logic to a nested if block instead of an earl…
Christian-Holbrook Nov 21, 2024
6d38fd4
Use $ instead of [[ ]]
Christian-Holbrook Nov 21, 2024
4e272a9
Add clarity to a test why a `2` showed up
TwitchBronBron Nov 25, 2024
a2158b8
Fix some lint errors in loops
TwitchBronBron Nov 25, 2024
a11b74c
Rename `insertCustomVariablesHelpers` and make it async
TwitchBronBron Nov 25, 2024
8f89a08
Make `createEvaluateContainer` `name` only accept a string
TwitchBronBron Nov 25, 2024
6ce25ec
Rename custom variable utils file
TwitchBronBron Nov 25, 2024
552d2c3
Revert integer thing.
TwitchBronBron Nov 25, 2024
aa729f9
Add unit tests for createEvaulateContainer with different children types
Christian-Holbrook Nov 26, 2024
fb66bbf
Merge branch 'master' into evaluate-roSGNode-Children
Christian-Holbrook Dec 2, 2024
5a3366b
Add clarifying comment
Christian-Holbrook Dec 3, 2024
e943865
Merge branch 'master' into evaluate-roSGNode-Children
TwitchBronBron Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 60 additions & 1 deletion src/adapters/DebugProtocolAdapter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,7 +542,9 @@ describe('DebugProtocolAdapter', function() {
container?.children.map(x => x.evaluateName)
).to.eql([
'person["name"]',
'person["age"]'
'person["age"]',
//$count
'2'
]);
//the top level object should be an AA
expect(container.type).to.eql(VariableType.AssociativeArray);
Expand All @@ -559,4 +561,61 @@ describe('DebugProtocolAdapter', function() {
expect(container.children[1].children).to.eql([]);
});
});

it('creates evaluate container with keyType string', () => {
let container = adapter['createEvaluateContainer'](
{
isConst: false,
isContainer: true,
refCount: 1,
type: VariableType.AssociativeArray,
value: undefined,
childCount: 1,
keyType: VariableType.String,
name: 'm',
children: [{
isConst: false,
isContainer: true,
refCount: 1,
type: VariableType.AssociativeArray,
value: undefined,
childCount: 0,
keyType: VariableType.String,
name: 'child'
}]
},
'm',
undefined
);
expect(container.children[0].evaluateName).to.eql('m["child"]');
});

it('creates evaluate container with keyType integer', () => {
let container = adapter['createEvaluateContainer'](
{
isConst: false,
isContainer: true,
refCount: 1,
type: VariableType.AssociativeArray,
value: undefined,
childCount: 1,
keyType: VariableType.Integer,
name: 'm',
children: [{
isConst: false,
isContainer: true,
refCount: 1,
type: VariableType.AssociativeArray,
value: undefined,
childCount: 0,
keyType: VariableType.Integer,
name: 'child'
}]
},
'm',
undefined
);
expect(container.children[0].evaluateName).to.eql('m[0]');
});

});
16 changes: 9 additions & 7 deletions src/adapters/DebugProtocolAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { VariableType } from '../debugProtocol/events/responses/VariablesRespons
import type { TelnetAdapter } from './TelnetAdapter';
import type { DeviceInfo } from 'roku-deploy';
import type { ThreadsResponse } from '../debugProtocol/events/responses/ThreadsResponse';
import { insertCustomVariables } from './customVariableUtils';

/**
* A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it.
Expand Down Expand Up @@ -515,7 +516,7 @@ export class DebugProtocolAdapter {
let thread = await this.getThreadByThreadId(threadIndex);
let frames: StackFrame[] = [];
let stackTraceData = await this.client.getStackTrace(threadIndex);
for (let i = 0; i < stackTraceData?.data?.entries?.length ?? 0; i++) {
for (let i = 0; i < (stackTraceData?.data?.entries?.length ?? 0); i++) {
let frameData = stackTraceData.data.entries[i];
let stackFrame: StackFrame = {
frameId: this.nextFrameId++,
Expand Down Expand Up @@ -598,6 +599,7 @@ export class DebugProtocolAdapter {
//this is the top-level container, so there are no parent keys to this entry
undefined
);
await insertCustomVariables(this, expression, container);
return container;
}
}
Expand Down Expand Up @@ -636,7 +638,7 @@ export class DebugProtocolAdapter {
* @param name the name of this variable. For example, `alpha.beta.charlie`, this value would be `charlie`. For local vars, this is the root variable name (i.e. `alpha`)
* @param parentEvaluateName the string used to derive the parent, _excluding_ this variable's name (i.e. `alpha.beta` or `alpha[0]`)
*/
private createEvaluateContainer(variable: Variable, name: string, parentEvaluateName: string) {
private createEvaluateContainer(variable: Variable, name: string | number, parentEvaluateName: string) {
TwitchBronBron marked this conversation as resolved.
Show resolved Hide resolved
let value;
let variableType = variable.type;
if (variable.value === null) {
Expand All @@ -658,15 +660,15 @@ export class DebugProtocolAdapter {
//build full evaluate name for this var. (i.e. `alpha["beta"]` + ["charlie"]` === `alpha["beta"]["charlie"]`)
let evaluateName: string;
if (!parentEvaluateName?.trim()) {
evaluateName = name;
evaluateName = name?.toString();
} else if (typeof name === 'string') {
evaluateName = `${parentEvaluateName}["${name}"]`;
} else if (typeof name === 'number') {
evaluateName = `${parentEvaluateName}[${name}]`;
}

let container: EvaluateContainer = {
name: name ?? '',
name: name?.toString() ?? '',
evaluateName: evaluateName ?? '',
type: variableType ?? '',
value: value ?? null,
Expand All @@ -685,7 +687,7 @@ export class DebugProtocolAdapter {
const childVariable = variable.children[i];
const childContainer = this.createEvaluateContainer(
childVariable,
container.keyType === KeyType.integer ? i.toString() : childVariable.name,
container.keyType === KeyType.integer ? i : childVariable.name,
container.evaluateName
);
container.children.push(childContainer);
Expand Down Expand Up @@ -736,7 +738,7 @@ export class DebugProtocolAdapter {
return [];
}

for (let i = 0; i < threadsResponse.data?.threads?.length ?? 0; i++) {
for (let i = 0; i < (threadsResponse.data?.threads?.length ?? 0); i++) {
let threadInfo = threadsResponse.data.threads[i];
let thread = <Thread>{
// NOTE: On THREAD_ATTACHED events the threads request is marking the wrong thread as primary.
Expand Down Expand Up @@ -902,7 +904,7 @@ export class DebugProtocolAdapter {

//if the response was successful, and we have the correct number of breakpoints in the response
if (response.data.errorCode === ErrorCode.OK && response?.data?.breakpoints?.length === breakpoints.length) {
for (let i = 0; i < response?.data?.breakpoints?.length ?? 0; i++) {
for (let i = 0; i < (response?.data?.breakpoints?.length ?? 0); i++) {
const deviceBreakpoint = response.data.breakpoints[i];

if (typeof deviceBreakpoint?.id === 'number') {
Expand Down
40 changes: 40 additions & 0 deletions src/adapters/customVariableUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as semver from 'semver';
import { KeyType } from './DebugProtocolAdapter';
import type { DebugProtocolAdapter, EvaluateContainer } from './DebugProtocolAdapter';

/**
* Insert custom variables into the `EvaluateContainer`. Most of these are for compatibility with older versions of the BrightScript debug protocol,
* but occasionally can be for adding new functionality for properties that don't exist in the debug protocol. Some of these will run `evaluate` commands
* to look up the data for the custom variables.
*/
export async function insertCustomVariables(adapter: DebugProtocolAdapter, expression: string, container: EvaluateContainer): Promise<void> {
if (semver.satisfies(adapter?.activeProtocolVersion, '<3.3.0')) {
if (container?.value?.startsWith('roSGNode')) {
let nodeChildren = <EvaluateContainer>{
name: '$children',
type: 'roArray',
highLevelType: 'array',
keyType: KeyType.integer,
presentationHint: 'virtual',
evaluateName: `${expression}.getChildren(-1, 0)`,
children: []
};
container.children.push(nodeChildren);
}
if (container.elementCount > 0 || container.type === 'Array') {
let nodeCount = <EvaluateContainer>{
name: '$count',
evaluateName: container.elementCount.toString(),
type: 'number',
highLevelType: undefined,
keyType: undefined,
presentationHint: 'virtual',
value: container.elementCount.toString(),
elementCount: undefined,
children: []
};
container.children.push(nodeCount);
}
}
await Promise.resolve();
}
55 changes: 31 additions & 24 deletions src/debugSession/BrightScriptDebugSession.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1108,7 +1108,8 @@ export class BrightScriptDebugSession extends BaseDebugSession {
const vars = await (this.rokuAdapter as TelnetAdapter).getScopeVariables();

for (const varName of vars) {
let result = await this.rokuAdapter.getVariable(varName, -1);
let { evalArgs } = await this.evaluateExpressionToTempVar({ expression: varName, frameId: -1 }, util.getVariablePath(varName));
let result = await this.rokuAdapter.getVariable(evalArgs.expression, -1);
let tempVar = this.getVariableFromResult(result, -1);
childVariables.push(tempVar);
}
Expand All @@ -1126,7 +1127,8 @@ export class BrightScriptDebugSession extends BaseDebugSession {
logger.log('variable', v);
//query for child vars if we haven't done it yet.
if (v.childVariables.length === 0) {
let result = await this.rokuAdapter.getVariable(v.evaluateName, v.frameId);
let { evalArgs } = await this.evaluateExpressionToTempVar({ expression: v.evaluateName, frameId: v.frameId }, util.getVariablePath(v.evaluateName));
let result = await this.rokuAdapter.getVariable(evalArgs.expression, v.frameId);
let tempVar = this.getVariableFromResult(result, v.frameId);
tempVar.frameId = v.frameId;
v.childVariables = tempVar.childVariables;
Expand Down Expand Up @@ -1207,41 +1209,26 @@ export class BrightScriptDebugSession extends BaseDebugSession {

//is at debugger prompt
} else {
let variablePath = util.getVariablePath(args.expression);
if (!variablePath && util.isAssignableExpression(args.expression)) {
let varIndex = this.getNextVarIndex(args.frameId);
let arrayVarName = this.tempVarPrefix + 'eval';
if (varIndex === 0) {
const response = await this.rokuAdapter.evaluate(`${arrayVarName} = []`, args.frameId);
console.log(response);
}
let statement = `${arrayVarName}[${varIndex}] = ${args.expression}`;
args.expression = `${arrayVarName}[${varIndex}]`;
let commandResults = await this.rokuAdapter.evaluate(statement, args.frameId);
if (commandResults.type === 'error') {
throw new Error(commandResults.message);
}
variablePath = [arrayVarName, varIndex.toString()];
}
let { evalArgs, variablePath } = await this.evaluateExpressionToTempVar(args, util.getVariablePath(args.expression));

//if we found a variable path (e.g. ['a', 'b', 'c']) then do a variable lookup because it's faster and more widely supported than `evaluate`
if (variablePath) {
let refId = this.getEvaluateRefId(args.expression, args.frameId);
let refId = this.getEvaluateRefId(evalArgs.expression, evalArgs.frameId);
let v: AugmentedVariable;
//if we already looked this item up, return it
if (this.variables[refId]) {
v = this.variables[refId];
} else {
let result = await this.rokuAdapter.getVariable(args.expression, args.frameId);
let result = await this.rokuAdapter.getVariable(evalArgs.expression, evalArgs.frameId);
if (!result) {
throw new Error('Error: unable to evaluate expression');
}

v = this.getVariableFromResult(result, args.frameId);
v = this.getVariableFromResult(result, evalArgs.frameId);
//TODO - testing something, remove later
// eslint-disable-next-line camelcase
v.request_seq = response.request_seq;
v.frameId = args.frameId;
v.frameId = evalArgs.frameId;
}
response.body = {
result: v.value,
Expand All @@ -1253,13 +1240,13 @@ export class BrightScriptDebugSession extends BaseDebugSession {

//run an `evaluate` call
} else {
let commandResults = await this.rokuAdapter.evaluate(args.expression, args.frameId);
let commandResults = await this.rokuAdapter.evaluate(evalArgs.expression, evalArgs.frameId);

commandResults.message = util.trimDebugPrompt(commandResults.message);
if (args.context !== 'watch') {
//clear variable cache since this action could have side-effects
this.clearState();
this.sendInvalidatedEvent(null, args.frameId);
this.sendInvalidatedEvent(null, evalArgs.frameId);
}
//if the adapter captured output (probably only telnet), print it to the vscode debug console
if (typeof commandResults.message === 'string') {
Expand Down Expand Up @@ -1290,6 +1277,26 @@ export class BrightScriptDebugSession extends BaseDebugSession {
deferred.resolve();
}

private async evaluateExpressionToTempVar(args: DebugProtocol.EvaluateArguments, variablePath: string[]): Promise< { evalArgs: DebugProtocol.EvaluateArguments; variablePath: string[] } > {
let returnVal = { evalArgs: args, variablePath };
if (!variablePath && util.isAssignableExpression(args.expression)) {
let varIndex = this.getNextVarIndex(args.frameId);
let arrayVarName = this.tempVarPrefix + 'eval';
if (varIndex === 0) {
const response = await this.rokuAdapter.evaluate(`${arrayVarName} = []`, args.frameId);
console.log(response);
}
let statement = `${arrayVarName}[${varIndex}] = ${args.expression}`;
returnVal.evalArgs.expression = `${arrayVarName}[${varIndex}]`;
let commandResults = await this.rokuAdapter.evaluate(statement, args.frameId);
if (commandResults.type === 'error') {
throw new Error(commandResults.message);
}
returnVal.variablePath = [arrayVarName, varIndex.toString()];
}
return returnVal;
}

/**
* Called when the host stops debugging
* @param response
Expand Down
Loading