Skip to content

Commit c03893e

Browse files
committed
Add Copy Value as raw. Add future debug/hover/context menu. Fix var_export escaping. Extend adapter tests for evaluateRequest. Refactor evaluateRequest.
1 parent 573a6aa commit c03893e

File tree

5 files changed

+117
-77
lines changed

5 files changed

+117
-77
lines changed

package.json

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -572,12 +572,17 @@
572572
"debug/variables/context": [
573573
{
574574
"command": "extension.php-debug.copyVarExport",
575-
"group": "5_cutcopypaste@10",
575+
"group": "5_cutcopypaste@11",
576576
"when": "debugType == php"
577577
},
578578
{
579579
"command": "extension.php-debug.copyJson",
580-
"group": "5_cutcopypaste@11",
580+
"group": "5_cutcopypaste@12",
581+
"when": "debugType == php"
582+
},
583+
{
584+
"command": "extension.php-debug.copyRaw",
585+
"group": "5_cutcopypaste@13",
581586
"when": "debugType == php"
582587
}
583588
],
@@ -591,6 +596,28 @@
591596
"command": "extension.php-debug.copyJson",
592597
"group": "3_modification@52",
593598
"when": "debugType == php"
599+
},
600+
{
601+
"command": "extension.php-debug.copyRaw",
602+
"group": "3_modification@53",
603+
"when": "debugType == php"
604+
}
605+
],
606+
"debug/hover/context": [
607+
{
608+
"command": "extension.php-debug.copyVarExport",
609+
"group": "5_cutcopypaste@11",
610+
"when": "debugType == php"
611+
},
612+
{
613+
"command": "extension.php-debug.copyJson",
614+
"group": "5_cutcopypaste@12",
615+
"when": "debugType == php"
616+
},
617+
{
618+
"command": "extension.php-debug.copyRaw",
619+
"group": "5_cutcopypaste@13",
620+
"when": "debugType == php"
594621
}
595622
]
596623
},
@@ -621,6 +648,10 @@
621648
{
622649
"command": "extension.php-debug.copyJson",
623650
"title": "Copy Value as json_encode"
651+
},
652+
{
653+
"command": "extension.php-debug.copyRaw",
654+
"title": "Copy Value as raw"
624655
}
625656
],
626657
"keybindings": [

src/extension.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,12 @@ export function activate(context: vscode.ExtensionContext) {
189189
}
190190
)
191191
)
192+
context.subscriptions.push(
193+
vscode.commands.registerCommand(
194+
'extension.php-debug.copyRaw',
195+
async (arg: IVariablesContext, p2: any, p3: any) => {
196+
await copyVar(arg, 'clipboard-raw')
197+
}
198+
)
199+
)
192200
}

src/phpDebug.ts

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ if (process.env['VSCODE_NLS_CONFIG']) {
3232
}
3333

3434
/** formats a xdebug property value for VS Code */
35-
function formatPropertyValue(property: xdebug.BaseProperty): string {
35+
function formatPropertyValue(property: xdebug.BaseProperty, quoteString: boolean = true): string {
3636
let displayValue: string
3737
if (property.hasChildren || property.type === 'array' || property.type === 'object') {
3838
if (property.type === 'array') {
@@ -48,7 +48,7 @@ function formatPropertyValue(property: xdebug.BaseProperty): string {
4848
} else {
4949
// for null, uninitialized, resource, etc. show the type
5050
displayValue = property.value || property.type === 'string' ? property.value : property.type
51-
if (property.type === 'string') {
51+
if (property.type === 'string' && quoteString) {
5252
displayValue = `"${displayValue}"`
5353
} else if (property.type === 'bool') {
5454
displayValue = Boolean(parseInt(displayValue, 10)).toString()
@@ -1525,56 +1525,54 @@ class PhpDebugSession extends vscode.DebugSession {
15251525
const stackFrame = this._stackFrames.get(args.frameId)!
15261526
const connection = stackFrame.connection
15271527
let result: xdebug.BaseProperty | null = null
1528+
15281529
if (args.context === 'hover') {
15291530
// try to get variable from property_get
15301531
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
15311532
const res = await connection.sendPropertyGetNameCommand(args.expression, ctx[0])
15321533
if (res.property) {
15331534
result = res.property
15341535
}
1535-
} else if (args.context === 'repl') {
1536-
const uuid = randomUUID()
1537-
await connection.sendEvalCommand(`$GLOBALS['eval_cache']['${uuid}']=${args.expression}`)
1538-
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
1539-
const res = await connection.sendPropertyGetNameCommand(`$eval_cache['${uuid}']`, ctx[1])
1540-
if (res.property) {
1541-
result = res.property
1542-
}
1543-
} else if (args.context === 'clipboard-var_export') {
1544-
const property =
1545-
this.getPropertyFromReference(args.variablesReference) ??
1546-
(await (async () => {
1547-
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
1536+
} else {
1537+
let property = this.getPropertyFromReference(args.variablesReference)
1538+
let ctx
1539+
if (!property) {
1540+
// try to get variable
1541+
ctx = await stackFrame.getContexts() // TODO CACHE THIS
1542+
try {
1543+
// we might need to try other contexts too?
15481544
const res = await connection.sendPropertyGetNameCommand(args.expression, ctx[0])
1549-
return res.property
1550-
})())
1551-
response.body = { result: await varExportProperty(property), variablesReference: 0 }
1545+
property = res.property
1546+
} catch {
1547+
// ignore we failed, lets try evaling
1548+
}
1549+
}
1550+
if (!property) {
1551+
const uuid = randomUUID()
1552+
await connection.sendEvalCommand(`$GLOBALS['eval_cache']['${uuid}']=${args.expression}`)
1553+
const res = await connection.sendPropertyGetNameCommand(`$eval_cache['${uuid}']`, ctx![1])
1554+
property = res.property
1555+
}
1556+
result = property
1557+
}
1558+
1559+
if (result && args.context === 'clipboard-var_export') {
1560+
response.body = { result: await varExportProperty(result as xdebug.Property), variablesReference: 0 }
15521561
this.sendResponse(response)
15531562
return
1554-
} else if (args.context === 'clipboard-json') {
1555-
const property =
1556-
this.getPropertyFromReference(args.variablesReference) ??
1557-
(await (async () => {
1558-
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
1559-
const res = await connection.sendPropertyGetNameCommand(args.expression, ctx[0])
1560-
return res.property
1561-
})())
1562-
response.body = { result: await varJsonProperty(property), variablesReference: 0 }
1563+
} else if (result && args.context === 'clipboard-json') {
1564+
response.body = { result: await varJsonProperty(result as xdebug.Property), variablesReference: 0 }
1565+
this.sendResponse(response)
1566+
return
1567+
} else if (result && args.context === 'clipboard-raw') {
1568+
response.body = { result: formatPropertyValue(result, false), variablesReference: 0 }
1569+
this.sendResponse(response)
1570+
return
1571+
} else if (result && this._initializeArgs.clientID !== 'vscode' && args.context === 'clipboard') {
1572+
// special case for NON-vscode clients where we cant add extra clipboard related contexts and var_export should be the default
1573+
response.body = { result: await varExportProperty(result as xdebug.Property), variablesReference: 0 }
15631574
this.sendResponse(response)
15641575
return
1565-
} else if (args.context === 'watch') {
1566-
const uuid = randomUUID()
1567-
await connection.sendEvalCommand(`$GLOBALS['eval_cache']['watch']['${uuid}']=${args.expression}`)
1568-
const ctx = await stackFrame.getContexts() // TODO CACHE THIS
1569-
const res = await connection.sendPropertyGetNameCommand(`$eval_cache['watch']['${uuid}']`, ctx[1])
1570-
if (res.property) {
1571-
result = res.property
1572-
}
1573-
} else {
1574-
const res = await connection.sendEvalCommand(args.expression)
1575-
if (res.result) {
1576-
result = res.result
1577-
}
15781576
}
15791577

15801578
if (result) {

src/test/adapter.ts

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -831,41 +831,45 @@ describe('PHP Debug Adapter', () => {
831831
await client.configurationDoneRequest()
832832
const { frame } = await assertStoppedLocation('breakpoint', program, 19)
833833

834-
const response = (
835-
await client.evaluateRequest({
836-
context: 'clipboard',
837-
frameId: frame.id,
838-
expression: '$anInt',
839-
})
840-
).body
841-
842-
assert.equal(response.result, '123')
843-
assert.equal(response.variablesReference, 0)
844-
845-
const response2 = (
846-
await client.evaluateRequest({
847-
context: 'clipboard',
848-
frameId: frame.id,
849-
expression: '$aString',
850-
})
851-
).body
852834

853-
assert.equal(response2.result, "'123'")
854-
assert.equal(response2.variablesReference, 0)
835+
interface TestCase {
836+
context: string
837+
expression: string
838+
result: string
839+
hasVariablesReference: boolean
840+
}
855841

856-
const response3 = (
857-
await client.evaluateRequest({
858-
context: 'clipboard',
859-
frameId: frame.id,
860-
expression: '$anArray',
861-
})
862-
).body
842+
const testCases: TestCase[] = [
843+
{ context: 'hover', expression: '$anInt', result: '123', hasVariablesReference: false },
844+
{ context: 'hover', expression: '$aString', result: '"123"', hasVariablesReference: false },
845+
{ context: 'hover', expression: '$anArray', result: 'array(3)', hasVariablesReference: true },
846+
{ context: 'clipboard', expression: '$anInt', result: '123', hasVariablesReference: false },
847+
{ context: 'clipboard', expression: '$aString', result: "'123'", hasVariablesReference: false },
848+
{ context: 'clipboard', expression: '$anArray', result: 'array (\n 0 => 1,\n test => 2,\n test2 => \n array (\n t => 123,\n ),\n)', hasVariablesReference: false },
849+
{ context: 'clipboard-json', expression: '$anInt', result: '123', hasVariablesReference: false },
850+
{ context: 'clipboard-json', expression: '$aString', result: "\"123\"", hasVariablesReference: false },
851+
{ context: 'clipboard-json', expression: '$anArray', result: '{\n "0": 1,\n "test": 2,\n "test2": {\n "t": 123\n }\n}', hasVariablesReference: false },
852+
{ context: 'clipboard-raw', expression: '$anInt', result: '123', hasVariablesReference: false },
853+
{ context: 'clipboard-raw', expression: '$aString', result: "123", hasVariablesReference: false },
854+
{ context: 'clipboard-raw', expression: '$anArray', result: 'array(3)', hasVariablesReference: false },
855+
]
856+
857+
for (const testCase of testCases) {
858+
const response = (
859+
await client.evaluateRequest({
860+
context: testCase.context as any,
861+
frameId: frame.id,
862+
expression: testCase.expression,
863+
})
864+
).body
863865

864-
assert.equal(
865-
response3.result,
866-
'array (\n 0 => 1,\n test => 2,\n test2 => \n array (\n t => 123,\n ),\n)'
867-
)
868-
assert.equal(response3.variablesReference, 0)
866+
assert.equal(response.result, testCase.result, `Failed for ${testCase.context} - ${testCase.expression}`)
867+
if (testCase.hasVariablesReference) {
868+
assert.notEqual(response.variablesReference, 0, `Expected variablesReference for ${testCase.context} - ${testCase.expression}`)
869+
} else {
870+
assert.equal(response.variablesReference, 0, `Unexpected variablesReference for ${testCase.context} - ${testCase.expression}`)
871+
}
872+
}
869873
})
870874
})
871875

src/varExport.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,15 @@ export async function varExportProperty(property: xdebug.Property, indent: strin
4242
// for null, uninitialized, resource, etc. show the type
4343
displayValue = property.value || property.type === 'string' ? property.value : property.type
4444
if (property.type === 'string') {
45-
// escaping ?
4645
if (property.size > property.value.length) {
47-
// get value
4846
const p2 = await property.context.stackFrame.connection.sendPropertyValueNameCommand(
4947
property.fullName,
5048
property.context
5149
)
5250
displayValue = p2.value
5351
}
54-
displayValue = `'${displayValue}'`
52+
const escaped = displayValue.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
53+
displayValue = `'${escaped}'`
5554
} else if (property.type === 'bool') {
5655
displayValue = Boolean(parseInt(displayValue, 10)).toString()
5756
}

0 commit comments

Comments
 (0)