From c020954091ca24e7b87a2cf9d8890b84e7ceb472 Mon Sep 17 00:00:00 2001 From: Damjan Cvetko Date: Mon, 1 Jul 2024 01:55:13 +0200 Subject: [PATCH 1/3] feat: DBGp stream redirect support --- src/phpDebug.ts | 7 +++++++ src/xdebugConnection.ts | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/phpDebug.ts b/src/phpDebug.ts index a04eed72..ac741a39 100644 --- a/src/phpDebug.ts +++ b/src/phpDebug.ts @@ -561,6 +561,13 @@ class PhpDebugSession extends vscode.DebugSession { throw new Error(`Error applying xdebugSettings: ${String(error instanceof Error ? error.message : error)}`) } + if (this._args.externalConsole) { + await connection.sendStdout('1') + connection.on('stream', (stream: xdebug.Stream) => + this.sendEvent(new vscode.OutputEvent(stream.value, 'stdout')) + ) + } + this.sendEvent(new vscode.ThreadEvent('started', connection.id)) // wait for all breakpoints diff --git a/src/xdebugConnection.ts b/src/xdebugConnection.ts index f1908a4b..35d93413 100644 --- a/src/xdebugConnection.ts +++ b/src/xdebugConnection.ts @@ -179,6 +179,24 @@ export class UserNotify extends Notify { } } +export class Stream { + /** Type of stream */ + type: string + /** Data of the stream */ + value: string + + /** Constructs a stream object from an XML node from a Xdebug response */ + constructor(document: XMLDocument) { + this.type = document.documentElement.getAttribute('type')! + const encoding = document.documentElement.getAttribute('encoding') + if (encoding) { + this.value = iconv.encode(document.documentElement.textContent!, encoding).toString() + } else { + this.value = document.documentElement.textContent! + } + } +} + export type BreakpointType = 'line' | 'call' | 'return' | 'exception' | 'conditional' | 'watch' export type BreakpointState = 'enabled' | 'disabled' export type BreakpointResolved = 'resolved' | 'unresolved' @@ -745,6 +763,7 @@ export declare interface Connection extends DbgpConnection { on(event: 'log', listener: (text: string) => void): this on(event: 'notify_user', listener: (notify: UserNotify) => void): this on(event: 'notify_breakpoint_resolved', listener: (notify: BreakpointResolvedNotify) => void): this + on(event: 'stream', listener: (stream: Stream) => void): this } /** @@ -809,6 +828,9 @@ export class Connection extends DbgpConnection { } else if (response.documentElement.nodeName === 'notify') { const n = Notify.fromXml(response, this) this.emit('notify_' + n.name, n) + } else if (response.documentElement.nodeName === 'stream') { + const s = new Stream(response) + this.emit('stream', s) } else { const transactionId = parseInt(response.documentElement.getAttribute('transaction_id')!) if (this._pendingCommands.has(transactionId)) { @@ -1109,4 +1131,14 @@ export class Connection extends DbgpConnection { public async sendEvalCommand(expression: string): Promise { return new EvalResponse(await this._enqueueCommand('eval', undefined, expression), this) } + + // ------------------------------ stream ---------------------------------------- + + public async sendStdout(mode: '0' | '1' | '2'): Promise { + return new Response(await this._enqueueCommand('stdout', `-c ${mode}`), this) + } + + public async sendStderr(mode: '0' | '1' | '2'): Promise { + return new Response(await this._enqueueCommand('stderr', `-c ${mode}`), this) + } } From b679620b7cfb8be75ca4c1b3679c3291b32127ad Mon Sep 17 00:00:00 2001 From: Damjan Cvetko Date: Mon, 15 Jul 2024 00:58:00 +0200 Subject: [PATCH 2/3] Test stream --- src/test/adapter.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/test/adapter.ts b/src/test/adapter.ts index c4c8ad9f..14acdd2e 100644 --- a/src/test/adapter.ts +++ b/src/test/adapter.ts @@ -5,7 +5,8 @@ import { DebugClient } from '@vscode/debugadapter-testsupport' import { DebugProtocol } from '@vscode/debugprotocol' import * as semver from 'semver' import * as net from 'net' -import { describe, it, beforeEach, afterEach } from 'mocha' +import * as childProcess from 'child_process' +import { describe, it, beforeEach, afterEach, after } from 'mocha' chai.use(chaiAsPromised) const assert = chai.assert @@ -822,6 +823,20 @@ describe('PHP Debug Adapter', () => { }) }) + describe('stream tests', () => { + const program = path.join(TEST_PROJECT, 'output.php') + + it('listen with externalConsole', async () => { + // this is how we can currently turn on stdout redirect + await Promise.all([client.launch({ externalConsole: true }), client.configurationSequence()]) + + const script = childProcess.spawn('php', [program]) + after(() => script.kill()) + await client.assertOutput('stdout', 'stdout output 1') + await client.assertOutput('stdout', 'stdout output 2') + }) + }) + describe('special adapter tests', () => { it('max connections', async () => { await Promise.all([client.launch({ maxConnections: 1, log: true }), client.configurationSequence()]) From d5f252dbec6673f9682444a287b8f07fe40018e3 Mon Sep 17 00:00:00 2001 From: Damjan Cvetko Date: Mon, 15 Jul 2024 22:56:01 +0200 Subject: [PATCH 3/3] Expose stdout stream setting --- package.json | 11 +++++++++++ src/phpDebug.ts | 10 ++++++++-- src/test/adapter.ts | 2 +- src/xdebugConnection.ts | 4 ++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 107e0641..ba783cb5 100644 --- a/package.json +++ b/package.json @@ -366,6 +366,17 @@ "xdebugCloudToken": { "type": "string", "description": "Xdebug Could token" + }, + "stream": { + "type": "object", + "description": "Xdebug stream settings", + "properties": { + "stdout": { + "type": "number", + "description": "Redirect stdout stream: 0 (disable), 1 (copy), 2 (redirect)", + "default": 0 + } + } } } } diff --git a/src/phpDebug.ts b/src/phpDebug.ts index ac741a39..5fc152f2 100644 --- a/src/phpDebug.ts +++ b/src/phpDebug.ts @@ -98,6 +98,10 @@ export interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchReques maxConnections?: number /** Xdebug cloud token */ xdebugCloudToken?: string + /** Xdebug stream settings */ + stream?: { + stdout?: 0 | 1 | 2 + } // CLI options @@ -561,8 +565,10 @@ class PhpDebugSession extends vscode.DebugSession { throw new Error(`Error applying xdebugSettings: ${String(error instanceof Error ? error.message : error)}`) } - if (this._args.externalConsole) { - await connection.sendStdout('1') + const stdout = + this._args.stream?.stdout === undefined ? (this._args.externalConsole ? 1 : 0) : this._args.stream.stdout + if (stdout) { + await connection.sendStdout(stdout) connection.on('stream', (stream: xdebug.Stream) => this.sendEvent(new vscode.OutputEvent(stream.value, 'stdout')) ) diff --git a/src/test/adapter.ts b/src/test/adapter.ts index 14acdd2e..f343b03a 100644 --- a/src/test/adapter.ts +++ b/src/test/adapter.ts @@ -828,7 +828,7 @@ describe('PHP Debug Adapter', () => { it('listen with externalConsole', async () => { // this is how we can currently turn on stdout redirect - await Promise.all([client.launch({ externalConsole: true }), client.configurationSequence()]) + await Promise.all([client.launch({ stream: { stdout: '1' } }), client.configurationSequence()]) const script = childProcess.spawn('php', [program]) after(() => script.kill()) diff --git a/src/xdebugConnection.ts b/src/xdebugConnection.ts index 35d93413..1df7e760 100644 --- a/src/xdebugConnection.ts +++ b/src/xdebugConnection.ts @@ -1134,11 +1134,11 @@ export class Connection extends DbgpConnection { // ------------------------------ stream ---------------------------------------- - public async sendStdout(mode: '0' | '1' | '2'): Promise { + public async sendStdout(mode: 0 | 1 | 2): Promise { return new Response(await this._enqueueCommand('stdout', `-c ${mode}`), this) } - public async sendStderr(mode: '0' | '1' | '2'): Promise { + public async sendStderr(mode: 0 | 1 | 2): Promise { return new Response(await this._enqueueCommand('stderr', `-c ${mode}`), this) } }