Skip to content

Commit a5e1455

Browse files
committed
Adapt VS Code January release 0.10.8, implement SourceRequest
1 parent 938e7fb commit a5e1455

File tree

5 files changed

+87
-72
lines changed

5 files changed

+87
-72
lines changed

.vscode/launch.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"runtimeArgs": [
99
"--harmony"
1010
],
11-
"program": "./src/phpDebug.ts",
11+
"program": "${workspaceRoot}/src/phpDebug.ts",
1212
"stopOnEntry": false,
1313
"args": [
1414
"--server=4711"
@@ -17,7 +17,7 @@
1717
"NODE_ENV": "development"
1818
},
1919
"sourceMaps": true,
20-
"outDir": "./out"
20+
"outDir": "${workspaceRoot}/out"
2121
},
2222
{
2323
"name": "Launch Extension",
@@ -28,21 +28,21 @@
2828
"--extensionDevelopmentPath=${workspaceRoot}"
2929
],
3030
"sourceMaps": true,
31-
"outDir": "./out"
31+
"outDir": "${workspaceRoot}/out"
3232
},
3333
{
3434
"name": "Run Tests",
3535
"type": "node",
3636
"request": "launch",
37-
"program": "node_modules/mocha/bin/_mocha",
37+
"program": "${workspaceRoot}node_modules/mocha/bin/_mocha",
3838
"args": [
3939
"./out/tests",
4040
"--timeout",
4141
"999999",
4242
"--colors"
4343
],
4444
"sourceMaps": true,
45-
"outDir": "./out"
45+
"outDir": "${workspaceRoot}/out"
4646
}
4747
]
4848
}

README.md

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,9 @@ If you want to debug a running application on a remote host, you have to set the
5252
Example:
5353
```json
5454
"serverSourceRoot": "/var/www/myproject",
55-
"localSourceRoot": "./src"
55+
"localSourceRoot": "${workspaceRoot}/src"
5656
```
57-
`localSourceRoot` is resolved relative to the project root (the currently opened folder in VS Code).
5857
Both paths are normalized, so you can use slashes or backslashes no matter of the OS you're running.
59-
If no `localSourceRoot` is specified, the project root is assumed.
6058

6159
Troubleshooting
6260
---------------

package.json

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"email": "[email protected]"
1515
},
1616
"engines": {
17-
"vscode": "^0.10.6",
17+
"vscode": "^0.10.8",
1818
"node": "^4.1.1"
1919
},
2020
"icon": "images/logo.svg",
@@ -34,14 +34,14 @@
3434
},
3535
"dependencies": {
3636
"iconv-lite": "^0.4.13",
37-
"moment": "^2.10.6",
37+
"moment": "^2.11.2",
3838
"url-relative": "^1.0.0",
39-
"vscode-debugadapter": "^1.0.3",
40-
"vscode-debugprotocol": "^1.0.1",
41-
"xmldom": "^0.1.19"
39+
"vscode-debugadapter": "^1.5.0",
40+
"vscode-debugprotocol": "^1.5.0",
41+
"xmldom": "^0.1.22"
4242
},
4343
"devDependencies": {
44-
"typescript": "^1.6.2"
44+
"typescript": "^1.7.5"
4545
},
4646
"scripts": {
4747
"compile": "tsc -p ./src",
@@ -82,7 +82,7 @@
8282
},
8383
"localSourceRoot": {
8484
"type": "string",
85-
"description": "The source root on this machine that is the equivalent to the serverSourceRoot on the server. May be relative to the project root."
85+
"description": "The source root on this machine that is the equivalent to the serverSourceRoot on the server."
8686
},
8787
"cwd": {
8888
"type": "string",

src/phpDebug.ts

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,17 @@ function formatPropertyValue(property: xdebug.BaseProperty): string {
4949
*/
5050
interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchRequestArguments {
5151
/** The port where the adapter should listen for XDebug connections (default: 9000) */
52-
port: number;
52+
port?: number;
5353
/** Automatically stop target after launch. If not specified, target does not stop. */
5454
stopOnEntry?: boolean;
5555
/** The source root on the server when doing remote debugging on a different host */
5656
serverSourceRoot?: string;
57-
/** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. May be relative to cwd. */
57+
/** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. */
5858
localSourceRoot?: string;
5959
/** The current working directory, by default the project root */
6060
cwd?: string;
6161
/** If true, will log all communication between VS Code and the adapter to the console */
62-
log: boolean;
62+
log?: boolean;
6363
}
6464

6565
class PhpDebugSession extends vscode.DebugSession {
@@ -72,16 +72,19 @@ class PhpDebugSession extends vscode.DebugSession {
7272

7373
/**
7474
* A map from VS Code thread IDs to XDebug Connections.
75-
* XDebug makes a new connection for each request to the webserver, we present hese as threads to VS Code.
75+
* XDebug makes a new connection for each request to the webserver, we present these as threads to VS Code.
7676
* The threadId key is equal to the id attribute of the connection.
7777
*/
7878
private _connections = new Map<number, xdebug.Connection>();
7979

80-
/** A set of connecitons which still need to be initialized with exception breakpoints before _runOrStopOnEntry can be called. */
81-
private _connectionsAwaitingExceptionBreakpoints = new Set<xdebug.Connection>();
80+
/** A set of connections which are not yet running and are waiting for configurationDoneRequest */
81+
private _waitingConnections = new Set<xdebug.Connection>();
8282

83-
/** A set of connecitons which still need to be initialized with exception breakpoints before _runOrStopOnEntry can be called. */
84-
private _connectionsAwaitingBreakpoints = new Set<xdebug.Connection>();
83+
/** A counter for unique source IDs */
84+
private _sourceIdCounter = 1;
85+
86+
/** A map of VS Code source IDs to XDebug file URLs for virtual files (dpgp://whatever) and the corresponding connection */
87+
private _sources = new Map<number, {connection: xdebug.Connection, url: string}>();
8588

8689
/** A counter for unique stackframe IDs */
8790
private _stackFrameIdCounter = 1;
@@ -105,28 +108,25 @@ class PhpDebugSession extends vscode.DebugSession {
105108
super(debuggerLinesStartAt1, isServer);
106109
}
107110

111+
protected initializeRequest(response: VSCodeDebugProtocol.InitializeResponse, args: VSCodeDebugProtocol.InitializeRequestArguments): void {
112+
response.body.supportsConfigurationDoneRequest = true;
113+
response.body.supportsEvaluateForHovers = true;
114+
this.sendResponse(response);
115+
}
116+
108117
protected attachRequest(response: VSCodeDebugProtocol.AttachResponse, args: VSCodeDebugProtocol.AttachRequestArguments) {
109118
this.sendErrorResponse(response, 0, 'Attach requests are not supported');
110119
this.shutdown();
111120
}
112121

113122
protected launchRequest(response: VSCodeDebugProtocol.LaunchResponse, args: LaunchRequestArguments): void {
114-
if (args.serverSourceRoot) {
115-
// use cwd by default for localSourceRoot
116-
if (!args.localSourceRoot) {
117-
args.localSourceRoot = '.';
118-
}
119-
// resolve localSourceRoot relative to the project root
120-
args.localSourceRoot = path.resolve(args.cwd, args.localSourceRoot);
121-
}
122123
this._args = args;
123124
const server = this._server = net.createServer();
124125
server.on('connection', (socket: net.Socket) => {
125126
// new XDebug connection
126127
const connection = new xdebug.Connection(socket);
127128
this._connections.set(connection.id, connection);
128-
this._connectionsAwaitingBreakpoints.add(connection);
129-
this._connectionsAwaitingExceptionBreakpoints.add(connection);
129+
this._waitingConnections.add(connection);
130130
connection.waitForInitPacket()
131131
.then(() => {
132132
this.sendEvent(new vscode.ThreadEvent('started', connection.id));
@@ -146,16 +146,6 @@ class PhpDebugSession extends vscode.DebugSession {
146146
this.sendResponse(response);
147147
}
148148

149-
/** is called after all breakpoints etc. are initialized and either runs the script or notifies VS Code that we stopped on entry, depending on launch settings */
150-
private _runOrStopOnEntry(connection: xdebug.Connection): void {
151-
// either tell VS Code we stopped on entry or run the script
152-
if (this._args.stopOnEntry) {
153-
this.sendEvent(new vscode.StoppedEvent('entry', connection.id));
154-
} else {
155-
connection.sendRunCommand().then(response => this._checkStatus(response));
156-
}
157-
}
158-
159149
/** Checks the status of a StatusResponse and notifies VS Code accordingly */
160150
private _checkStatus(response: xdebug.StatusResponse): void {
161151
const connection = response.connection;
@@ -182,9 +172,12 @@ class PhpDebugSession extends vscode.DebugSession {
182172
}
183173

184174
/** converts a server-side XDebug file URI to a local path for VS Code with respect to source root settings */
185-
protected convertDebuggerPathToClient(fileUri: string): string {
175+
protected convertDebuggerPathToClient(fileUri: string|url.Url): string {
176+
if (typeof fileUri === 'string') {
177+
fileUri = url.parse(<string>fileUri);
178+
}
186179
// convert the file URI to a path
187-
let serverPath = decodeURI(url.parse(fileUri).pathname);
180+
let serverPath = decodeURI((<url.Url>fileUri).pathname);
188181
// strip the trailing slash from Windows paths (indicated by a drive letter with a colon)
189182
if (/^\/[a-zA-Z]:\//.test(serverPath)) {
190183
serverPath = serverPath.substr(1);
@@ -225,7 +218,7 @@ class PhpDebugSession extends vscode.DebugSession {
225218
}
226219

227220
/** Logs all requests before dispatching */
228-
protected dispatchRequest(request: VSCodeDebugProtocol.Request) {
221+
protected dispatchRequest(request: VSCodeDebugProtocol.Request): void {
229222
const log = `-> ${request.command}Request\n${util.inspect(request, {depth: null})}\n\n`;
230223
console.log(log);
231224
if (this._args && this._args.log) {
@@ -243,7 +236,7 @@ class PhpDebugSession extends vscode.DebugSession {
243236
super.sendEvent(event);
244237
}
245238

246-
public sendResponse(response: VSCodeDebugProtocol.Response) {
239+
public sendResponse(response: VSCodeDebugProtocol.Response): void {
247240
const log = `<- ${response.command}Response\n${util.inspect(response, {depth: null})}\n\n`;
248241
console[response.success ? 'log' : 'error'](log);
249242
if (this._args && this._args.log) {
@@ -276,15 +269,6 @@ class PhpDebugSession extends vscode.DebugSession {
276269
.then(() => Promise.all(args.lines.map(line =>
277270
connection.sendBreakpointSetCommand({type: 'line', fileUri, line})
278271
.then(xdebugResponse => {
279-
// has this connection finally received its long-awaited breakpoints?
280-
if (this._connectionsAwaitingBreakpoints.has(connection)) {
281-
// remember that the breakpoints for this connection have been set
282-
this._connectionsAwaitingBreakpoints.delete(connection);
283-
// if this connection has already received exception breakpoints, run it now
284-
if (!this._connectionsAwaitingExceptionBreakpoints.has(connection)) {
285-
this._runOrStopOnEntry(connection);
286-
}
287-
}
288272
// only capture each breakpoint once
289273
if (connectionIndex === 0) {
290274
breakpoints.push(new vscode.Breakpoint(true, line));
@@ -333,24 +317,26 @@ class PhpDebugSession extends vscode.DebugSession {
333317
return connection.sendBreakpointSetCommand({type: 'exception', exception: '*'});
334318
}
335319
})
336-
.then(() => {
337-
// has this connection finally received its long-awaited exception breakpoints?
338-
if (this._connectionsAwaitingExceptionBreakpoints.has(connection)) {
339-
// remember that the exception breakpoints for this connection have been set
340-
this._connectionsAwaitingExceptionBreakpoints.delete(connection);
341-
// if this connection has already received line breakpoints, run it now
342-
if (!this._connectionsAwaitingBreakpoints.has(connection)) {
343-
this._runOrStopOnEntry(connection);
344-
}
345-
}
346-
})
347320
)).then(() => {
348321
this.sendResponse(response);
349322
}).catch(error => {
350323
this.sendErrorResponse(response, error.code, error.message);
351324
});
352325
}
353326

327+
/** Executed after all breakpoints have been set by VS Code */
328+
protected configurationDoneRequest(response: VSCodeDebugProtocol.ConfigurationDoneResponse, args: VSCodeDebugProtocol.ConfigurationDoneArguments): void {
329+
for (const connection of Array.from(this._waitingConnections)) {
330+
// either tell VS Code we stopped on entry or run the script
331+
if (this._args.stopOnEntry) {
332+
this.sendEvent(new vscode.StoppedEvent('entry', connection.id));
333+
} else {
334+
connection.sendRunCommand().then(response => this._checkStatus(response));
335+
}
336+
}
337+
this.sendResponse(response);
338+
}
339+
354340
/** Executed after a successfull launch or attach request and after a ThreadEvent */
355341
protected threadsRequest(response: VSCodeDebugProtocol.ThreadsResponse): void {
356342
// PHP doesn't have threads, but it may have multiple requests in parallel.
@@ -374,10 +360,18 @@ class PhpDebugSession extends vscode.DebugSession {
374360
// this._contexts.clear();
375361
response.body = {
376362
stackFrames: xdebugResponse.stack.map(stackFrame => {
377-
// XDebug paths are URIs, VS Code file paths
378-
const filePath = this.convertDebuggerPathToClient(stackFrame.fileUri);
379-
// "Name" of the source and the actual file path
380-
const source = new vscode.Source(path.basename(filePath), filePath);
363+
let source: vscode.Source;
364+
const urlObject = url.parse(stackFrame.fileUri);
365+
if (urlObject.protocol === 'dbgp:') {
366+
const sourceReference = this._sourceIdCounter++;
367+
this._sources.set(sourceReference, {connection, url: stackFrame.fileUri});
368+
source = new vscode.Source(stackFrame.name, stackFrame.fileUri.substr('dbgp://'.length), sourceReference, stackFrame.type);
369+
} else {
370+
// XDebug paths are URIs, VS Code file paths
371+
const filePath = this.convertDebuggerPathToClient(urlObject);
372+
// "Name" of the source and the actual file path
373+
source = new vscode.Source(path.basename(filePath), filePath);
374+
}
381375
// a new, unique ID for scopeRequests
382376
const stackFrameId = this._stackFrameIdCounter++;
383377
// save the connection this stackframe belongs to and the level of the stackframe under the stacktrace id
@@ -393,6 +387,14 @@ class PhpDebugSession extends vscode.DebugSession {
393387
});
394388
}
395389

390+
protected sourceRequest(response: VSCodeDebugProtocol.SourceResponse, args: VSCodeDebugProtocol.SourceArguments): void {
391+
const {connection, url} = this._sources.get(args.sourceReference);
392+
connection.sendSourceCommand(url).then(xdebugResponse => {
393+
response.body.content = xdebugResponse.source;
394+
this.sendResponse(response);
395+
});
396+
}
397+
396398
protected scopesRequest(response: VSCodeDebugProtocol.ScopesResponse, args: VSCodeDebugProtocol.ScopesArguments): void {
397399
const stackFrame = this._stackFrames.get(args.frameId);
398400
stackFrame.getContexts()
@@ -428,7 +430,7 @@ class PhpDebugSession extends vscode.DebugSession {
428430
} else if (this._evalResultProperties.has(variablesReference)) {
429431
// the children of properties returned from an eval command are always inlined, so we simply resolve them
430432
const property = this._evalResultProperties.get(variablesReference);
431-
propertiesPromise = Promise.resolve(property.children);
433+
propertiesPromise = Promise.resolve(property.hasChildren ? property.children : []);
432434
} else {
433435
console.error('Unknown variable reference: ' + variablesReference);
434436
console.error('Known variables: ' + JSON.stringify(Array.from(this._properties)));

src/xdebugConnection.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ export class BreakpointListResponse extends Response {
170170
export class StackFrame {
171171
/** The UI-friendly name of this stack frame, like a function name or "{main}" */
172172
name: string;
173+
/** The type of stack frame. Valid values are "file" and "eval" */
174+
type: string;
173175
/** The file URI where the stackframe was entered */
174176
fileUri: string;
175177
/** The line number inside file where the stackframe was entered */
@@ -185,6 +187,7 @@ export class StackFrame {
185187
constructor(stackFrameNode: Element, connection: Connection) {
186188
this.name = stackFrameNode.getAttribute('where');
187189
this.fileUri = stackFrameNode.getAttribute('filename');
190+
this.type = stackFrameNode.getAttribute('type');
188191
this.line = parseInt(stackFrameNode.getAttribute('lineno'));
189192
this.level = parseInt(stackFrameNode.getAttribute('level'));
190193
this.connection = connection;
@@ -209,6 +212,14 @@ export class StackGetResponse extends Response {
209212
}
210213
}
211214

215+
export class SourceResponse extends Response {
216+
source: string;
217+
constructor(document: XMLDocument, connection: Connection) {
218+
super(document, connection);
219+
this.source = document.documentElement.textContent;
220+
}
221+
}
222+
212223
/** A context inside a stack frame, like "Local" or "Superglobals" */
213224
export class Context {
214225
/** Unique id that is used for further commands */
@@ -615,6 +626,10 @@ export class Connection extends DbgpConnection {
615626
return this._enqueueCommand('stack_get').then(document => new StackGetResponse(document, this));
616627
}
617628

629+
public sendSourceCommand(uri: string): Promise<SourceResponse> {
630+
return this._enqueueCommand('source', `-f ${uri}`).then(document => new SourceResponse(document, this));
631+
}
632+
618633
// ------------------------------ context --------------------------------------
619634

620635
/** Sends a context_names command. */

0 commit comments

Comments
 (0)