Skip to content

Commit 2d3a471

Browse files
committed
feat: DBGp stream redirect support (#968)
* feat: DBGp stream redirect support * Test stream * Expose stdout stream setting
1 parent 5e99ab1 commit 2d3a471

File tree

4 files changed

+72
-1
lines changed

4 files changed

+72
-1
lines changed

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,17 @@
366366
"xdebugCloudToken": {
367367
"type": "string",
368368
"description": "Xdebug Could token"
369+
},
370+
"stream": {
371+
"type": "object",
372+
"description": "Xdebug stream settings",
373+
"properties": {
374+
"stdout": {
375+
"type": "number",
376+
"description": "Redirect stdout stream: 0 (disable), 1 (copy), 2 (redirect)",
377+
"default": 0
378+
}
379+
}
369380
}
370381
}
371382
}

src/phpDebug.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ export interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchReques
9898
maxConnections?: number
9999
/** Xdebug cloud token */
100100
xdebugCloudToken?: string
101+
/** Xdebug stream settings */
102+
stream?: {
103+
stdout?: 0 | 1 | 2
104+
}
101105

102106
// CLI options
103107

@@ -566,6 +570,15 @@ class PhpDebugSession extends vscode.DebugSession {
566570
throw new Error(`Error applying xdebugSettings: ${String(error instanceof Error ? error.message : error)}`)
567571
}
568572

573+
const stdout =
574+
this._args.stream?.stdout === undefined ? (this._args.externalConsole ? 1 : 0) : this._args.stream.stdout
575+
if (stdout) {
576+
await connection.sendStdout(stdout)
577+
connection.on('stream', (stream: xdebug.Stream) =>
578+
this.sendEvent(new vscode.OutputEvent(stream.value, 'stdout'))
579+
)
580+
}
581+
569582
this.sendEvent(new vscode.ThreadEvent('started', connection.id))
570583

571584
// wait for all breakpoints

src/test/adapter.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { DebugClient } from '@vscode/debugadapter-testsupport'
55
import { DebugProtocol } from '@vscode/debugprotocol'
66
import * as semver from 'semver'
77
import * as net from 'net'
8-
import { describe, it, beforeEach, afterEach } from 'mocha'
8+
import * as childProcess from 'child_process'
9+
import { describe, it, beforeEach, afterEach, after } from 'mocha'
910
chai.use(chaiAsPromised)
1011
const assert = chai.assert
1112

@@ -832,6 +833,20 @@ describe('PHP Debug Adapter', () => {
832833
})
833834
})
834835

836+
describe('stream tests', () => {
837+
const program = path.join(TEST_PROJECT, 'output.php')
838+
839+
it('listen with externalConsole', async () => {
840+
// this is how we can currently turn on stdout redirect
841+
await Promise.all([client.launch({ stream: { stdout: '1' } }), client.configurationSequence()])
842+
843+
const script = childProcess.spawn('php', [program])
844+
after(() => script.kill())
845+
await client.assertOutput('stdout', 'stdout output 1')
846+
await client.assertOutput('stdout', 'stdout output 2')
847+
})
848+
})
849+
835850
describe('special adapter tests', () => {
836851
it('max connections', async () => {
837852
await Promise.all([client.launch({ maxConnections: 1, log: true }), client.configurationSequence()])

src/xdebugConnection.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,24 @@ export class UserNotify extends Notify {
179179
}
180180
}
181181

182+
export class Stream {
183+
/** Type of stream */
184+
type: string
185+
/** Data of the stream */
186+
value: string
187+
188+
/** Constructs a stream object from an XML node from a Xdebug response */
189+
constructor(document: XMLDocument) {
190+
this.type = document.documentElement.getAttribute('type')!
191+
const encoding = document.documentElement.getAttribute('encoding')
192+
if (encoding) {
193+
this.value = iconv.encode(document.documentElement.textContent!, encoding).toString()
194+
} else {
195+
this.value = document.documentElement.textContent!
196+
}
197+
}
198+
}
199+
182200
export type BreakpointType = 'line' | 'call' | 'return' | 'exception' | 'conditional' | 'watch'
183201
export type BreakpointState = 'enabled' | 'disabled'
184202
export type BreakpointResolved = 'resolved' | 'unresolved'
@@ -745,6 +763,7 @@ export declare interface Connection extends DbgpConnection {
745763
on(event: 'log', listener: (text: string) => void): this
746764
on(event: 'notify_user', listener: (notify: UserNotify) => void): this
747765
on(event: 'notify_breakpoint_resolved', listener: (notify: BreakpointResolvedNotify) => void): this
766+
on(event: 'stream', listener: (stream: Stream) => void): this
748767
}
749768

750769
/**
@@ -809,6 +828,9 @@ export class Connection extends DbgpConnection {
809828
} else if (response.documentElement.nodeName === 'notify') {
810829
const n = Notify.fromXml(response, this)
811830
this.emit('notify_' + n.name, n)
831+
} else if (response.documentElement.nodeName === 'stream') {
832+
const s = new Stream(response)
833+
this.emit('stream', s)
812834
} else {
813835
const transactionId = parseInt(response.documentElement.getAttribute('transaction_id')!)
814836
if (this._pendingCommands.has(transactionId)) {
@@ -1115,4 +1137,14 @@ export class Connection extends DbgpConnection {
11151137
public async sendEvalCommand(expression: string): Promise<EvalResponse> {
11161138
return new EvalResponse(await this._enqueueCommand('eval', undefined, expression), this)
11171139
}
1140+
1141+
// ------------------------------ stream ----------------------------------------
1142+
1143+
public async sendStdout(mode: 0 | 1 | 2): Promise<Response> {
1144+
return new Response(await this._enqueueCommand('stdout', `-c ${mode}`), this)
1145+
}
1146+
1147+
public async sendStderr(mode: 0 | 1 | 2): Promise<Response> {
1148+
return new Response(await this._enqueueCommand('stderr', `-c ${mode}`), this)
1149+
}
11181150
}

0 commit comments

Comments
 (0)