Skip to content

Commit f6be72f

Browse files
felixfbeckerzobo
authored andcommitted
feat: add support for setVariablesRequest (#70)
* Add support for setVariablesRequest * Add pending tests * Code fixes after rebase. * Readme. * Tests for setVariables * Changelog.
1 parent 30b3dc7 commit f6be72f

File tree

5 files changed

+137
-2
lines changed

5 files changed

+137
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
77
## [1.23.0]
88

99
- When `env` is specified in launch configuration it will be merged the process environment.
10+
- Set variable support.
1011

1112
## [1.22.0]
1213

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Options specific to CLI debugging:
115115
- Arrays & objects (including classname, private and static properties)
116116
- Debug console
117117
- Watches
118+
- Set variables
118119
- Run as CLI
119120
- Run without debugging
120121
- DBGp Proxy registration and unregistration support

src/phpDebug.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class PhpDebugSession extends vscode.DebugSession {
195195
supportsFunctionBreakpoints: true,
196196
supportsLogPoints: true,
197197
supportsHitConditionalBreakpoints: true,
198+
supportsSetVariable: true,
198199
exceptionBreakpointFilters: [
199200
{
200201
filter: 'Notice',
@@ -891,6 +892,42 @@ class PhpDebugSession extends vscode.DebugSession {
891892
this.sendResponse(response)
892893
}
893894

895+
protected async setVariableRequest(
896+
response: VSCodeDebugProtocol.SetVariableResponse,
897+
args: VSCodeDebugProtocol.SetVariableArguments
898+
) {
899+
try {
900+
let properties: xdebug.Property[]
901+
if (this._properties.has(args.variablesReference)) {
902+
// variablesReference is a property
903+
const container = this._properties.get(args.variablesReference)!
904+
if (!container.hasChildren) {
905+
throw new Error('Cannot edit property without children')
906+
}
907+
if (container.children.length === container.numberOfChildren) {
908+
properties = container.children
909+
} else {
910+
properties = await container.getChildren()
911+
}
912+
} else if (this._contexts.has(args.variablesReference)) {
913+
const context = this._contexts.get(args.variablesReference)!
914+
properties = await context.getProperties()
915+
} else {
916+
throw new Error('Unknown variable reference')
917+
}
918+
const property = properties.find(child => child.name === args.name)
919+
if (!property) {
920+
throw new Error('Property not found')
921+
}
922+
await property.set(args.value)
923+
response.body = { value: args.value }
924+
} catch (error) {
925+
this.sendErrorResponse(response, error)
926+
return
927+
}
928+
this.sendResponse(response)
929+
}
930+
894931
protected async variablesRequest(
895932
response: VSCodeDebugProtocol.VariablesResponse,
896933
args: VSCodeDebugProtocol.VariablesArguments
@@ -927,6 +964,8 @@ class PhpDebugSession extends vscode.DebugSession {
927964
} else {
928965
properties = []
929966
}
967+
// SHOULD WE CACHE?
968+
property.children = <xdebug.Property[]>properties
930969
} else if (this._evalResultProperties.has(variablesReference)) {
931970
// the children of properties returned from an eval command are always inlined, so we simply resolve them
932971
const property = this._evalResultProperties.get(variablesReference)!

src/test/adapter.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,72 @@ describe('PHP Debug Adapter', () => {
664664
}
665665
})
666666

667+
describe('setVariables', () => {
668+
const program = path.join(TEST_PROJECT, 'variables.php')
669+
670+
let localScope: DebugProtocol.Scope | undefined
671+
let localVariables: DebugProtocol.Variable[]
672+
let variables: { [name: string]: string } = Object.create(null)
673+
674+
beforeEach(async () => {
675+
client.launch({ program })
676+
await client.waitForEvent('initialized')
677+
await client.setBreakpointsRequest({ source: { path: program }, breakpoints: [{ line: 19 }] })
678+
const [, event] = await Promise.all([
679+
client.configurationDoneRequest(),
680+
client.waitForEvent('stopped') as Promise<DebugProtocol.StoppedEvent>,
681+
])
682+
const stackFrame = (await client.stackTraceRequest({ threadId: event.body.threadId! })).body.stackFrames[0]
683+
const scopes = (await client.scopesRequest({ frameId: stackFrame.id })).body.scopes
684+
localScope = scopes.find(scope => scope.name === 'Locals')
685+
})
686+
687+
async function getLocals() {
688+
localVariables = (await client.variablesRequest({ variablesReference: localScope!.variablesReference }))
689+
.body.variables
690+
variables = Object.create(null)
691+
for (const variable of localVariables) {
692+
variables[variable.name] = variable.value
693+
}
694+
}
695+
696+
it('should set the value of an integer', async () => {
697+
await getLocals()
698+
assert.propertyVal(variables, '$anInt', '123')
699+
await client.setVariableRequest({
700+
variablesReference: localScope!.variablesReference,
701+
name: '$anInt',
702+
value: '100',
703+
})
704+
await getLocals()
705+
assert.propertyVal(variables, '$anInt', '100')
706+
})
707+
it('should set the value of a string', async () => {
708+
await getLocals()
709+
assert.propertyVal(variables, '$aString', '"123"')
710+
await client.setVariableRequest({
711+
variablesReference: localScope!.variablesReference,
712+
name: '$aString',
713+
value: '"aaaa"',
714+
})
715+
await getLocals()
716+
assert.propertyVal(variables, '$aString', '"aaaa"')
717+
})
718+
it('should set the value of an nested property', async () => {
719+
await getLocals()
720+
let anArray = localVariables.find(variable => variable.name === '$anArray')
721+
assert.propertyVal(anArray!, 'value', 'array(3)')
722+
await client.setVariableRequest({
723+
variablesReference: localScope!.variablesReference,
724+
name: '$anArray',
725+
value: 'array(1,2)',
726+
})
727+
await getLocals()
728+
anArray = localVariables.find(variable => variable.name === '$anArray')
729+
assert.propertyVal(anArray!, 'value', 'array(2)')
730+
})
731+
})
732+
667733
describe('virtual sources', () => {
668734
it('should break on an exception inside eval code')
669735
it('should return the eval code with a source request')

src/xdebugConnection.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,14 @@ export class Property extends BaseProperty {
576576
)
577577
}
578578
}
579+
580+
/**
581+
* Sets the value of this property through a property_set command
582+
*/
583+
public set(value: string): Promise<Response> {
584+
return this.context.stackFrame.connection.sendPropertySetCommand(this, value)
585+
}
586+
579587
/**
580588
* Returns the child properties of this property by doing another property_get
581589
* @returns Promise.<Property[]>
@@ -706,6 +714,13 @@ interface Command {
706714
isExecuteCommand: boolean
707715
}
708716

717+
/**
718+
* Escapes a value to pass it as an argument in an XDebug command
719+
*/
720+
function escape(value: string): string {
721+
return '"' + value.replace(/("|\\)/g, '\\$1') + '"'
722+
}
723+
709724
/**
710725
* This class represents a connection to Xdebug and is instantiated with a socket.
711726
*/
@@ -1021,18 +1036,31 @@ export class Connection extends DbgpConnection {
10211036
)
10221037
}
10231038

1039+
// ------------------------------ property --------------------------------------
1040+
10241041
/** Sends a property_get command */
10251042
public async sendPropertyGetCommand(property: Property): Promise<PropertyGetResponse> {
1026-
const escapedFullName = '"' + property.fullName.replace(/("|\\)/g, '\\$1') + '"'
10271043
return new PropertyGetResponse(
10281044
await this._enqueueCommand(
10291045
'property_get',
1030-
`-d ${property.context.stackFrame.level} -c ${property.context.id} -n ${escapedFullName}`
1046+
`-d ${property.context.stackFrame.level} -c ${property.context.id} -n ${escape(property.fullName)}`
10311047
),
10321048
property
10331049
)
10341050
}
10351051

1052+
/** Sends a property_set command */
1053+
public async sendPropertySetCommand(property: Property, value: string): Promise<Response> {
1054+
return new Response(
1055+
await this._enqueueCommand(
1056+
'property_set',
1057+
`-d ${property.context.stackFrame.level} -c ${property.context.id} -n ${escape(property.fullName)}`,
1058+
value
1059+
),
1060+
property.context.stackFrame.connection
1061+
)
1062+
}
1063+
10361064
// ------------------------------- eval -----------------------------------------
10371065

10381066
/** sends an eval command */

0 commit comments

Comments
 (0)