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

feat: support clipboard context on variable copy operation #917

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/phpDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ class PhpDebugSession extends vscode.DebugSession {
],
supportTerminateDebuggee: true,
supportsDelayedStackTraceLoading: false,
supportsClipboardContext: true,
}
this.sendResponse(response)
}
Expand Down Expand Up @@ -1441,6 +1442,17 @@ class PhpDebugSession extends vscode.DebugSession {
if (response.property) {
result = response.property
}
} else if (args.context === 'clipboard') {
const uuid = randomUUID()
await connection.sendEvalCommand(
`$GLOBALS['eval_cache']['${uuid}']=var_export(${args.expression}, true)`
)
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
const res = await connection.sendPropertyValueNameCommand(`$eval_cache['${uuid}']`, ctx[1])
// force a string response
response.body = { result: res.value, variablesReference: 0 }
this.sendResponse(response)
return
} else {
const response = await connection.sendEvalCommand(args.expression)
if (response.result) {
Expand Down
48 changes: 47 additions & 1 deletion src/test/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,52 @@ describe('PHP Debug Adapter', () => {
assert.deepEqual(vars.body.variables[0].name, '0')
assert.deepEqual(vars.body.variables[0].value, '1')
})
it('should return the eval result for clipboard', async () => {
const program = path.join(TEST_PROJECT, 'variables.php')

await client.launch({
program,
})
await client.setBreakpointsRequest({ source: { path: program }, breakpoints: [{ line: 19 }] })
await client.configurationDoneRequest()
const { frame } = await assertStoppedLocation('breakpoint', program, 19)

const response = (
await client.evaluateRequest({
context: 'clipboard',
frameId: frame.id,
expression: '$anInt',
})
).body

assert.equal(response.result, '123')
assert.equal(response.variablesReference, 0)

const response2 = (
await client.evaluateRequest({
context: 'clipboard',
frameId: frame.id,
expression: '$aString',
})
).body

assert.equal(response2.result, "'123'")
assert.equal(response2.variablesReference, 0)

const response3 = (
await client.evaluateRequest({
context: 'clipboard',
frameId: frame.id,
expression: '$anArray',
})
).body

assert.equal(
response3.result,
"array (\n 0 => 1,\n 'test' => 2,\n 'test2' => \n array (\n 't' => 123,\n ),\n)"
)
assert.equal(response3.variablesReference, 0)
})
})

describe.skip('output events', () => {
Expand All @@ -827,7 +873,7 @@ describe('PHP Debug Adapter', () => {
await Promise.all([client.launch({ maxConnections: 1, log: true }), client.configurationSequence()])

const s1 = net.createConnection({ port: 9003 })
await client.assertOutput('console', 'new connection 1 from ')
await client.assertOutput('console', 'new connection ')
net.createConnection({ port: 9003 })
const o = await client.waitForEvent('output')
assert.match(
Expand Down
39 changes: 39 additions & 0 deletions src/xdebugConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,33 @@ export class PropertyGetNameResponse extends Response {
}
}

/** The response to a property_value command */
export class PropertyValueResponse extends Response {
/** the size of the value */
size: number
/** the data type of the variable. Can be string, int, float, bool, array, object, uninitialized, null or resource */
type: string
/** the value of the property for primitive types */
value: string
constructor(document: XMLDocument, connection: Connection) {
super(document, connection)
if (document.documentElement.hasAttribute('size')) {
this.size = parseInt(document.documentElement.getAttribute('size') ?? '0')
}
this.type = document.documentElement.getAttribute('type') ?? ''
if (document.documentElement.getElementsByTagName('value').length > 0) {
this.value = decodeTag(document.documentElement, 'value')
} else {
const encoding = document.documentElement.getAttribute('encoding')
if (encoding) {
this.value = iconv.encode(document.documentElement.textContent!, encoding).toString()
} else {
this.value = document.documentElement.textContent!
}
}
}
}

/** class for properties returned from eval commands. These don't have a full name or an ID, but have all children already inlined. */
export class EvalResultProperty extends BaseProperty {
children: EvalResultProperty[]
Expand Down Expand Up @@ -1091,6 +1118,18 @@ export class Connection extends DbgpConnection {
)
}

/** Sends a property_value by name command */
public async sendPropertyValueNameCommand(name: string, context: Context): Promise<PropertyValueResponse> {
const escapedFullName = '"' + name.replace(/("|\\)/g, '\\$1') + '"'
return new PropertyValueResponse(
await this._enqueueCommand(
'property_value',
`-d ${context.stackFrame.level} -c ${context.id} -n ${escapedFullName}`
),
context.stackFrame.connection
)
}

/** Sends a property_set command */
public async sendPropertySetCommand(property: Property, value: string): Promise<Response> {
return new Response(
Expand Down