Skip to content

Commit 37bc38f

Browse files
committed
POC implementation of debug session separation.
One DAP session listens for connection and initiates a new DAP session via custom NewDbgpConnectionEvent. The extension activate/deactivate is created to short-circuit starting on new DAP sessions and forcing them to all be in-process.
1 parent ec385ca commit 37bc38f

File tree

4 files changed

+154
-15
lines changed

4 files changed

+154
-15
lines changed

package-lock.json

Lines changed: 11 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"@types/semver": "^7.3.4",
5858
"@types/urlencode": "^1.1.2",
5959
"@types/xmldom": "^0.1.30",
60+
"@types/vscode": "^1.53.0",
6061
"chai": "^4.3.0",
6162
"chai-as-promised": "^7.1.1",
6263
"husky": "^4.3.8",
@@ -130,6 +131,10 @@
130131
"out/test/**/*.*"
131132
]
132133
},
134+
"main": "./out/extension.js",
135+
"activationEvents": [
136+
"onDebug"
137+
],
133138
"contributes": {
134139
"breakpoints": [
135140
{
@@ -302,12 +307,12 @@
302307
"port": 0,
303308
"runtimeArgs": [
304309
"-dxdebug.start_with_request=yes"
305-
],
306-
"env": {
307-
"XDEBUG_MODE": "debug,develop",
308-
"XDEBUG_CONFIG": "^\"client_port=\\${port\\}\""
309-
}
310+
],
311+
"env": {
312+
"XDEBUG_MODE": "debug,develop",
313+
"XDEBUG_CONFIG": "^\"client_port=\\${port\\}\""
310314
}
315+
}
311316
},
312317
{
313318
"label": "PHP: Launch currently open script with Xdebug 2",

src/extension.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import * as vscode from 'vscode'
2+
import { PhpDebugSession} from './phpDebug'
3+
4+
export function activate(context: vscode.ExtensionContext) {
5+
console.log('activate')
6+
7+
context.subscriptions.push(vscode.debug.onDidStartDebugSession(session => {
8+
console.log('onDidStartDebugSession', session)
9+
//session.customRequest('test1', { test2: "test3" })
10+
}))
11+
context.subscriptions.push(vscode.debug.onDidTerminateDebugSession(session => {
12+
console.log('onDidTerminateDebugSession', session)
13+
}))
14+
15+
context.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent(event => {
16+
console.log('onDidReceiveDebugSessionCustomEvent', event)
17+
if (event.event === 'newDbgpConnection') {
18+
const config: vscode.DebugConfiguration = {
19+
...event.session.configuration
20+
}
21+
config.request = 'attach'
22+
config.name = 'DBGp connection ' + event.body.connId
23+
config.connId = event.body.connId
24+
vscode.debug.startDebugging(undefined, config, event.session)
25+
}
26+
}))
27+
28+
const factory = new InlineDebugAdapterFactory()
29+
context.subscriptions.push(vscode.debug.registerDebugAdapterDescriptorFactory('php', factory));
30+
if ('dispose' in factory) {
31+
context.subscriptions.push(factory);
32+
}
33+
}
34+
35+
export function deactivate() {
36+
console.log('deactivate')
37+
}
38+
39+
class InlineDebugAdapterFactory implements vscode.DebugAdapterDescriptorFactory {
40+
41+
createDebugAdapterDescriptor(_session: vscode.DebugSession): vscode.ProviderResult<vscode.DebugAdapterDescriptor> {
42+
// since DebugAdapterInlineImplementation is proposed API, a cast to <any> is required for now
43+
return <any>new vscode.DebugAdapterInlineImplementation(new PhpDebugSession());
44+
}
45+
}

src/phpDebug.ts

Lines changed: 88 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ function formatPropertyValue(property: xdebug.BaseProperty): string {
4646
return displayValue
4747
}
4848

49+
class NewDbgpConnectionEvent extends vscode.Event {
50+
body: {
51+
connId: number
52+
}
53+
constructor(connId: number) {
54+
super('newDbgpConnection');
55+
this.body = {
56+
connId: connId
57+
}
58+
}
59+
}
60+
4961
/**
5062
* This interface should always match the schema found in the mock-debug extension manifest.
5163
*/
@@ -85,9 +97,11 @@ interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchRequestArgume
8597
env?: { [key: string]: string }
8698
/** If true launch the target in an external console. */
8799
externalConsole?: boolean
100+
/** Internal variable for passing connections between adapter instances */
101+
connId?: number
88102
}
89103

90-
class PhpDebugSession extends vscode.DebugSession {
104+
export class PhpDebugSession extends vscode.DebugSession {
91105
/** The arguments that were given to launchRequest */
92106
private _args: LaunchRequestArguments
93107

@@ -143,6 +157,9 @@ class PhpDebugSession extends vscode.DebugSession {
143157
/** A flag to indicate that the adapter has already processed the stopOnEntry step request */
144158
private _hasStoppedOnEntry = false
145159

160+
/** Static store of all active connections used to pass them betweeen adapter instances */
161+
private static _allConnections = new Map<number, xdebug.Connection>()
162+
146163
public constructor() {
147164
super()
148165
this.setDebuggerColumnsStartAt1(true)
@@ -186,12 +203,69 @@ class PhpDebugSession extends vscode.DebugSession {
186203
this.sendResponse(response)
187204
}
188205

189-
protected attachRequest(
206+
protected async attachRequest(
190207
response: VSCodeDebugProtocol.AttachResponse,
191-
args: VSCodeDebugProtocol.AttachRequestArguments
208+
args2: VSCodeDebugProtocol.AttachRequestArguments
192209
) {
193-
this.sendErrorResponse(response, new Error('Attach requests are not supported'))
194-
this.shutdown()
210+
const args = args2 as LaunchRequestArguments
211+
if (!args.connId || !PhpDebugSession._allConnections.has(args.connId)) {
212+
this.sendErrorResponse(response, new Error('Cant find connection'))
213+
this.shutdown()
214+
return
215+
}
216+
this.sendResponse(response)
217+
218+
this._args = args
219+
const connection = PhpDebugSession._allConnections.get(args.connId!)!
220+
221+
this._connections.set(connection.id, connection)
222+
this._waitingConnections.add(connection)
223+
const disposeConnection = (error?: Error) => {
224+
if (this._connections.has(connection.id)) {
225+
if (args.log) {
226+
this.sendEvent(new vscode.OutputEvent('connection ' + connection.id + ' closed\n'))
227+
}
228+
if (error) {
229+
this.sendEvent(
230+
new vscode.OutputEvent(
231+
'connection ' + connection.id + ': ' + error.message + '\n'
232+
)
233+
)
234+
}
235+
this.sendEvent(new vscode.ContinuedEvent(connection.id, false))
236+
this.sendEvent(new vscode.ThreadEvent('exited', connection.id))
237+
connection.close()
238+
this._connections.delete(connection.id)
239+
this._waitingConnections.delete(connection)
240+
}
241+
PhpDebugSession._allConnections.delete(connection.id)
242+
this.sendEvent(new vscode.TerminatedEvent())
243+
}
244+
connection.on('warning', (warning: string) => {
245+
this.sendEvent(new vscode.OutputEvent(warning + '\n'))
246+
})
247+
connection.on('error', disposeConnection)
248+
connection.on('close', disposeConnection)
249+
await connection.waitForInitPacket()
250+
251+
// override features from launch.json
252+
try {
253+
const xdebugSettings = args.xdebugSettings || {}
254+
await Promise.all(
255+
Object.keys(xdebugSettings).map(setting =>
256+
connection.sendFeatureSetCommand(setting, xdebugSettings[setting])
257+
)
258+
)
259+
} catch (error) {
260+
throw new Error(
261+
'Error applying xdebugSettings: ' + (error instanceof Error ? error.message : error)
262+
)
263+
}
264+
265+
this.sendEvent(new vscode.ThreadEvent('started', connection.id))
266+
267+
// request breakpoints from VS Code
268+
this.sendEvent(new vscode.InitializedEvent())
195269
}
196270

197271
protected async launchRequest(response: VSCodeDebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
@@ -263,6 +337,9 @@ class PhpDebugSession extends vscode.DebugSession {
263337
if (args.log) {
264338
this.sendEvent(new vscode.OutputEvent('new connection ' + connection.id + '\n'), true)
265339
}
340+
PhpDebugSession._allConnections.set(connection.id, connection)
341+
this.sendEvent(new NewDbgpConnectionEvent(connection.id))
342+
/*
266343
this._connections.set(connection.id, connection)
267344
this._waitingConnections.add(connection)
268345
const disposeConnection = (error?: Error) => {
@@ -282,6 +359,7 @@ class PhpDebugSession extends vscode.DebugSession {
282359
connection.close()
283360
this._connections.delete(connection.id)
284361
this._waitingConnections.delete(connection)
362+
PhpDebugSession._allConnections.delete(connection.id)
285363
}
286364
}
287365
connection.on('warning', (warning: string) => {
@@ -309,6 +387,7 @@ class PhpDebugSession extends vscode.DebugSession {
309387
310388
// request breakpoints from VS Code
311389
await this.sendEvent(new vscode.InitializedEvent())
390+
*/
312391
} catch (error) {
313392
this.sendEvent(
314393
new vscode.OutputEvent((error instanceof Error ? error.message : error) + '\n', 'stderr')
@@ -1076,6 +1155,10 @@ class PhpDebugSession extends vscode.DebugSession {
10761155
this.sendErrorResponse(response, error)
10771156
}
10781157
}
1158+
1159+
protected customRequest(command: string, response: VSCodeDebugProtocol.Response, args: any): void {
1160+
console.log('test', args)
1161+
}
10791162
}
10801163

10811164
vscode.DebugSession.run(PhpDebugSession)

0 commit comments

Comments
 (0)