Skip to content
Merged
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
63 changes: 63 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,57 @@
"command": "extension.php-debug.runEditorContents",
"when": "resourceLangId == php && !inDiffEditor && resourceScheme == file"
}
],
"debug/variables/context": [
{
"command": "extension.php-debug.copyVarExport",
"group": "5_cutcopypaste@11",
"when": "debugType == php"
},
{
"command": "extension.php-debug.copyJson",
"group": "5_cutcopypaste@12",
"when": "debugType == php"
},
{
"command": "extension.php-debug.copyRaw",
"group": "5_cutcopypaste@13",
"when": "debugType == php"
}
],
"debug/watch/context": [
{
"command": "extension.php-debug.copyVarExport",
"group": "3_modification@51",
"when": "debugType == php"
},
{
"command": "extension.php-debug.copyJson",
"group": "3_modification@52",
"when": "debugType == php"
},
{
"command": "extension.php-debug.copyRaw",
"group": "3_modification@53",
"when": "debugType == php"
}
],
"debug/hover/context": [
{
"command": "extension.php-debug.copyVarExport",
"group": "5_cutcopypaste@11",
"when": "debugType == php"
},
{
"command": "extension.php-debug.copyJson",
"group": "5_cutcopypaste@12",
"when": "debugType == php"
},
{
"command": "extension.php-debug.copyRaw",
"group": "5_cutcopypaste@13",
"when": "debugType == php"
}
]
},
"commands": [
Expand All @@ -589,6 +640,18 @@
"category": "PHP Debug",
"enablement": "!inDebugMode",
"icon": "$(play)"
},
{
"command": "extension.php-debug.copyVarExport",
"title": "Copy Value as var_export"
},
{
"command": "extension.php-debug.copyJson",
"title": "Copy Value as json_encode"
},
{
"command": "extension.php-debug.copyRaw",
"title": "Copy Value as raw"
}
],
"keybindings": [
Expand Down
56 changes: 55 additions & 1 deletion src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import * as vscode from 'vscode'
import { WorkspaceFolder, DebugConfiguration, CancellationToken } from 'vscode'
import { LaunchRequestArguments } from './phpDebug'
import { EvaluateExtendedArguments, LaunchRequestArguments } from './phpDebug'
import * as which from 'which'
import * as path from 'path'
import { DebugProtocol } from '@vscode/debugprotocol'

export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(
Expand Down Expand Up @@ -143,4 +144,57 @@ export function activate(context: vscode.ExtensionContext) {
})
})
)

/* This is coppied from vscode/src/vs/workbench/contrib/debug/browser/variablesView.ts */
interface IVariablesContext {
sessionId: string | undefined
container: DebugProtocol.Variable | DebugProtocol.Scope | DebugProtocol.EvaluateArguments
variable: DebugProtocol.Variable
}

/* This is coppied from @vscode/debugprotocol/lib/debugProtocol.d.ts because customRequest returns the body of the response and not the response itself */
interface EvaluateResponse {
/** The result of the evaluate request. */
result: string
}

const copyVar = async (arg: IVariablesContext, context: string) => {
const aci = vscode.debug.activeStackItem
if (aci && aci instanceof vscode.DebugStackFrame) {
const ret = (await vscode.debug.activeDebugSession?.customRequest('evaluate', <EvaluateExtendedArguments>{
context,
expression: arg.variable.evaluateName,
frameId: aci.frameId,
variablesReference: arg.variable.variablesReference,
})) as EvaluateResponse
await vscode.env.clipboard.writeText(ret.result)
} else {
await vscode.window.showErrorMessage('Cannot derermine active debug session')
}
}

context.subscriptions.push(
vscode.commands.registerCommand(
'extension.php-debug.copyVarExport',
async (arg: IVariablesContext, p2: any, p3: any) => {
await copyVar(arg, 'clipboard-var_export')
}
)
)
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.php-debug.copyJson',
async (arg: IVariablesContext, p2: any, p3: any) => {
await copyVar(arg, 'clipboard-json')
}
)
)
context.subscriptions.push(
vscode.commands.registerCommand(
'extension.php-debug.copyRaw',
async (arg: IVariablesContext, p2: any, p3: any) => {
await copyVar(arg, 'clipboard-raw')
}
)
)
}
83 changes: 56 additions & 27 deletions src/phpDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { XdebugCloudConnection } from './cloud'
import { shouldIgnoreException } from './ignore'
import { varExportProperty } from './varExport'
import { supportedEngine } from './xdebugUtils'
import { varJsonProperty } from './varJson'

if (process.env['VSCODE_NLS_CONFIG']) {
try {
Expand All @@ -31,7 +32,7 @@ if (process.env['VSCODE_NLS_CONFIG']) {
}

/** formats a xdebug property value for VS Code */
function formatPropertyValue(property: xdebug.BaseProperty): string {
function formatPropertyValue(property: xdebug.BaseProperty, quoteString: boolean = true): string {
let displayValue: string
if (property.hasChildren || property.type === 'array' || property.type === 'object') {
if (property.type === 'array') {
Expand All @@ -47,7 +48,7 @@ function formatPropertyValue(property: xdebug.BaseProperty): string {
} else {
// for null, uninitialized, resource, etc. show the type
displayValue = property.value || property.type === 'string' ? property.value : property.type
if (property.type === 'string') {
if (property.type === 'string' && quoteString) {
displayValue = `"${displayValue}"`
} else if (property.type === 'bool') {
displayValue = Boolean(parseInt(displayValue, 10)).toString()
Expand All @@ -56,6 +57,11 @@ function formatPropertyValue(property: xdebug.BaseProperty): string {
return displayValue
}

export interface EvaluateExtendedArguments extends VSCodeDebugProtocol.EvaluateArguments {
/** The variable for which to retrieve its children. The `variablesReference` must have been obtained in the current suspended state. See 'Lifetime of Object References' in the Overview section for details. */
variablesReference?: number
}

/**
* This interface should always match the schema found in the mock-debug extension manifest.
*/
Expand Down Expand Up @@ -1496,9 +1502,18 @@ class PhpDebugSession extends vscode.DebugSession {
this.shutdown()
}

private getPropertyFromReference(variablesReference?: number): xdebug.Property | undefined {
if (variablesReference && this._properties.has(variablesReference)) {
return this._properties.get(variablesReference)!
} /*else if (variablesReference && this._evalResultProperties.has(variablesReference)) {
return this._evalResultProperties.get(variablesReference)!
}*/
return
}

protected async evaluateRequest(
response: VSCodeDebugProtocol.EvaluateResponse,
args: VSCodeDebugProtocol.EvaluateArguments
args: EvaluateExtendedArguments
): Promise<void> {
try {
if (!args.frameId) {
Expand All @@ -1510,40 +1525,54 @@ class PhpDebugSession extends vscode.DebugSession {
const stackFrame = this._stackFrames.get(args.frameId)!
const connection = stackFrame.connection
let result: xdebug.BaseProperty | null = null

if (args.context === 'hover') {
// try to get variable from property_get
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
const res = await connection.sendPropertyGetNameCommand(args.expression, ctx[0])
if (res.property) {
result = res.property
}
} else if (args.context === 'repl') {
const uuid = randomUUID()
await connection.sendEvalCommand(`$GLOBALS['eval_cache']['${uuid}']=${args.expression}`)
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
const res = await connection.sendPropertyGetNameCommand(`$eval_cache['${uuid}']`, ctx[1])
if (res.property) {
result = res.property
} else {
let property = this.getPropertyFromReference(args.variablesReference)
let ctx: xdebug.Context[]
if (!property) {
// try to get variable
ctx = await stackFrame.getContexts() // TODO CACHE THIS
try {
// we might need to try other contexts too?
const res = await connection.sendPropertyGetNameCommand(args.expression, ctx[0])
property = res.property
} catch {
// ignore we failed, lets try evaling
}
}
} else if (args.context === 'clipboard') {
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
const res = await connection.sendPropertyGetNameCommand(args.expression, ctx[0])
response.body = { result: await varExportProperty(res.property), variablesReference: 0 }
if (!property) {
const uuid = randomUUID()
await connection.sendEvalCommand(`$GLOBALS['eval_cache']['${uuid}']=${args.expression}`)
const res = await connection.sendPropertyGetNameCommand(`$eval_cache['${uuid}']`, ctx![1])
property = res.property
}
result = property
}

if (result && args.context === 'clipboard-var_export') {
response.body = { result: await varExportProperty(result as xdebug.Property), variablesReference: 0 }
this.sendResponse(response)
return
} else if (result && args.context === 'clipboard-json') {
response.body = { result: await varJsonProperty(result as xdebug.Property), variablesReference: 0 }
this.sendResponse(response)
return
} else if (result && args.context === 'clipboard-raw') {
response.body = { result: formatPropertyValue(result, false), variablesReference: 0 }
this.sendResponse(response)
return
} else if (result && this._initializeArgs.clientID !== 'vscode' && args.context === 'clipboard') {
// special case for NON-vscode clients where we cant add extra clipboard related contexts and var_export should be the default
response.body = { result: await varExportProperty(result as xdebug.Property), variablesReference: 0 }
this.sendResponse(response)
return
} else if (args.context === 'watch') {
const uuid = randomUUID()
await connection.sendEvalCommand(`$GLOBALS['eval_cache']['watch']['${uuid}']=${args.expression}`)
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
const res = await connection.sendPropertyGetNameCommand(`$eval_cache['watch']['${uuid}']`, ctx[1])
if (res.property) {
result = res.property
}
} else {
const res = await connection.sendEvalCommand(args.expression)
if (res.result) {
result = res.result
}
}

if (result) {
Expand Down
97 changes: 65 additions & 32 deletions src/test/adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -831,41 +831,74 @@ describe('PHP Debug Adapter', () => {
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)
interface TestCase {
context: string
expression: string
result: string
hasVariablesReference: boolean
}

const response2 = (
await client.evaluateRequest({
const testCases: TestCase[] = [
{ context: 'hover', expression: '$anInt', result: '123', hasVariablesReference: false },
{ context: 'hover', expression: '$aString', result: '"123"', hasVariablesReference: false },
{ context: 'hover', expression: '$anArray', result: 'array(3)', hasVariablesReference: true },
{ context: 'hover', expression: '$aBoolean', result: 'true', hasVariablesReference: false },
{ context: 'hover', expression: '$aFloat', result: '1.23', hasVariablesReference: false },
{ context: 'clipboard', expression: '$anInt', result: '123', hasVariablesReference: false },
{ context: 'clipboard', expression: '$aString', result: "'123'", hasVariablesReference: false },
{
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)
result: 'array (\n 0 => 1,\n test => 2,\n test2 => \n array (\n t => 123,\n ),\n)',
hasVariablesReference: false,
},
{ context: 'clipboard', expression: '$aBoolean', result: 'true', hasVariablesReference: false },
{ context: 'clipboard', expression: '$aFloat', result: '1.23', hasVariablesReference: false },
{ context: 'clipboard-json', expression: '$anInt', result: '123', hasVariablesReference: false },
{ context: 'clipboard-json', expression: '$aString', result: '"123"', hasVariablesReference: false },
{
context: 'clipboard-json',
expression: '$anArray',
result: '{\n "0": 1,\n "test": 2,\n "test2": {\n "t": 123\n }\n}',
hasVariablesReference: false,
},
{ context: 'clipboard-json', expression: '$aBoolean', result: 'true', hasVariablesReference: false },
{ context: 'clipboard-json', expression: '$aFloat', result: '1.23', hasVariablesReference: false },
{ context: 'clipboard-raw', expression: '$anInt', result: '123', hasVariablesReference: false },
{ context: 'clipboard-raw', expression: '$aString', result: '123', hasVariablesReference: false },
{ context: 'clipboard-raw', expression: '$anArray', result: 'array(3)', hasVariablesReference: false },
{ context: 'clipboard-raw', expression: '$aBoolean', result: 'true', hasVariablesReference: false },
{ context: 'clipboard-raw', expression: '$aFloat', result: '1.23', hasVariablesReference: false },
]

for (const testCase of testCases) {
const response = (
await client.evaluateRequest({
context: testCase.context as any,
frameId: frame.id,
expression: testCase.expression,
})
).body

assert.equal(
response.result,
testCase.result,
`Failed for ${testCase.context} - ${testCase.expression}`
)
if (testCase.hasVariablesReference) {
assert.notEqual(
response.variablesReference,
0,
`Expected variablesReference for ${testCase.context} - ${testCase.expression}`
)
} else {
assert.equal(
response.variablesReference,
0,
`Unexpected variablesReference for ${testCase.context} - ${testCase.expression}`
)
}
}
})
})

Expand Down
Loading