Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ In your project, go to the debugger and hit the little gear icon and choose _PHP
- `request`: Always `"launch"`
- `port`: The port on which to listen for XDebug (default: `9000`)
- `stopOnEntry`: Wether to break at the beginning of the script (default: `false`)
- `localSourceRoot`: The path to the folder that is being served by your webserver and maps to `serverSourceRoot` (for example `"${workspaceRoot}/public"`)
- `serverSourceRoot`: The path on the remote host where your webroot is located (for example `"/var/www"`)
- `pathMappings`: A list of server paths mapping to the local source paths on your machine, see "Remote Host Debugging" below
- `log`: Wether to log all communication between VS Code and the adapter to the debug console. See _Troubleshooting_ further down.
- `ignore`: An optional array of glob patterns that errors should be ignored from (for example `**/vendor/**/*.php`)
- `xdebugSettings`: Allows you to override XDebug's remote debugging settings to fine tuning XDebug to your needs. For example, you can play with `max_children` and `max_depth` to change the max number of array and object children that are retrieved and the max depth in structures like arrays and objects. This can speed up the debugger on slow machines.
Expand Down Expand Up @@ -82,10 +81,13 @@ Remote Host Debugging
---------------------
To debug a running application on a remote host, you need to tell XDebug to connect to a different IP than `localhost`. This can either be done by setting [`xdebug.remote_host`](https://xdebug.org/docs/remote#remote_host) to your IP or by setting [`xdebug.remote_connect_back = 1`](https://xdebug.org/docs/remote#remote_connect_back) to make XDebug always connect back to the machine who did the web request. The latter is the only setting that supports multiple users debugging the same server and "just works" for web projects. Again, please see the [XDebug documentation](https://xdebug.org/docs/remote#communcation) on the subject for more information.

To make VS Code map the files on the server to the right files on your local machine, you have to set the `localSourceRoot` and `serverSourceRoot` settings in your launch.json. Example:
To make VS Code map the files on the server to the right files on your local machine, you have to set the `pathMappings` settings in your launch.json. Example:
```json
"serverSourceRoot": "/var/www/myproject",
"localSourceRoot": "${workspaceRoot}/public"
// server -> local
"pathMappings": {
"/var/www/html": "{workspaceRoot}/www",
"/app": "{workspaceRoot}/app"
}
```
Please also note that setting any of the CLI debugging options will not work with remote host debugging, because the script is always launched locally. If you want to debug a CLI script on a remote host, you need to launch it manually from the command line.

Expand Down
11 changes: 9 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,18 @@
},
"serverSourceRoot": {
"type": "string",
"description": "The source root when debugging a remote host"
"description": "Deprecated: The source root when debugging a remote host",
"deprecationMessage": "Property serverSourceRoot is deprecated, please use pathMappings to define a server root."
},
"localSourceRoot": {
"type": "string",
"description": "The source root on this machine that is the equivalent to the serverSourceRoot on the server."
"description": "Deprecated: The source root on this machine that is the equivalent to the serverSourceRoot on the server.",
"deprecationMessage": "Property localSourceRoot is deprecated, please use pathMappings to define a local root."
},
"pathMappings": {
"type": "object",
"default": {},
"description": "A mapping of server paths to local paths."
},
"ignore": {
"type": "array",
Expand Down
31 changes: 29 additions & 2 deletions src/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import * as path from 'path';
import {decode} from 'urlencode';

/** converts a server-side XDebug file URI to a local path for VS Code with respect to source root settings */
export function convertDebuggerPathToClient(fileUri: string|url.Url, localSourceRoot?: string, serverSourceRoot?: string): string {
export function convertDebuggerPathToClient(fileUri: string|url.Url, pathMapping?: { [index: string]: string; }): string {
let localSourceRoot: string | undefined;
let serverSourceRoot: string | undefined;
if (typeof fileUri === 'string') {
fileUri = url.parse(fileUri);
}
Expand All @@ -17,6 +19,18 @@ export function convertDebuggerPathToClient(fileUri: string|url.Url, localSource
if (serverIsWindows) {
serverPath = serverPath.substr(1);
}
if (pathMapping) {
for (const mappedServerPath of Object.keys(pathMapping) ) {
const mappedLocalSource = pathMapping[mappedServerPath];
// normalize slashes for windows-to-unix
const serverRelative = (serverIsWindows ? path.win32 : path.posix).relative(mappedServerPath, serverPath);
if (serverRelative.indexOf('..') !== 0) {
serverSourceRoot = mappedServerPath;
localSourceRoot = mappedLocalSource;
break;
}
}
}
let localPath: string;
if (serverSourceRoot && localSourceRoot) {
// get the part of the path that is relative to the source root
Expand All @@ -30,9 +44,22 @@ export function convertDebuggerPathToClient(fileUri: string|url.Url, localSource
}

/** converts a local path from VS Code to a server-side XDebug file URI with respect to source root settings */
export function convertClientPathToDebugger(localPath: string, localSourceRoot?: string, serverSourceRoot?: string): string {
export function convertClientPathToDebugger(localPath: string, pathMapping?: { [index: string]: string; }): string {
let localSourceRoot: string | undefined;
let serverSourceRoot: string | undefined;
let localFileUri = fileUrl(localPath, {resolve: false});
let serverFileUri: string;
if (pathMapping) {
for (const mappedServerPath of Object.keys(pathMapping) ) {
const mappedLocalSource = pathMapping[mappedServerPath];
const localRelative = path.relative(mappedLocalSource, localPath);
if (localRelative.indexOf('..') !== 0) {
serverSourceRoot = mappedServerPath;
localSourceRoot = mappedLocalSource;
break;
}
}
}
if (serverSourceRoot && localSourceRoot) {
let localSourceRootUrl = fileUrl(localSourceRoot, {resolve: false});
if (!localSourceRootUrl.endsWith('/')) {
Expand Down
16 changes: 13 additions & 3 deletions src/phpDebug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ interface LaunchRequestArguments extends VSCodeDebugProtocol.LaunchRequestArgume
serverSourceRoot?: string;
/** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. */
localSourceRoot?: string;
/** The path to the source root on this machine that is the equivalent to the serverSourceRoot on the server. */
pathMappings?: { [index: string]: string };
/** If true, will log all communication between VS Code and the adapter to the console */
log?: boolean;
/** Array of glob patterns that errors should be ignored from */
Expand Down Expand Up @@ -183,6 +185,14 @@ class PhpDebugSession extends vscode.DebugSession {
}

protected async launchRequest(response: VSCodeDebugProtocol.LaunchResponse, args: LaunchRequestArguments) {
if (args.localSourceRoot && args.serverSourceRoot) {
let pathMappings: {[index: string]: string} = {};
if (args.pathMappings) {
pathMappings = args.pathMappings;
}
pathMappings[args.serverSourceRoot] = args.localSourceRoot;
args.pathMappings = pathMappings;
}
this._args = args;
/** launches the script as CLI */
const launchScript = async () => {
Expand Down Expand Up @@ -373,7 +383,7 @@ class PhpDebugSession extends vscode.DebugSession {
/** This is called for each source file that has breakpoints with all the breakpoints in that file and whenever these change. */
protected async setBreakPointsRequest(response: VSCodeDebugProtocol.SetBreakpointsResponse, args: VSCodeDebugProtocol.SetBreakpointsArguments) {
try {
const fileUri = convertClientPathToDebugger(args.source.path!, this._args.localSourceRoot, this._args.serverSourceRoot);
const fileUri = convertClientPathToDebugger(args.source.path!, this._args.pathMappings);
const connections = Array.from(this._connections.values());
let xdebugBreakpoints: Array<xdebug.ConditionalBreakpoint|xdebug.LineBreakpoint>;
response.body = {breakpoints: []};
Expand Down Expand Up @@ -559,7 +569,7 @@ class PhpDebugSession extends vscode.DebugSession {
line++;
} else {
// XDebug paths are URIs, VS Code file paths
const filePath = convertDebuggerPathToClient(urlObject, this._args.localSourceRoot, this._args.serverSourceRoot);
const filePath = convertDebuggerPathToClient(urlObject, this._args.pathMappings);
// "Name" of the source and the actual file path
source = {name: path.basename(filePath), path: filePath};
}
Expand All @@ -580,7 +590,7 @@ class PhpDebugSession extends vscode.DebugSession {
line++;
} else {
// XDebug paths are URIs, VS Code file paths
const filePath = convertDebuggerPathToClient(urlObject, this._args.localSourceRoot, this._args.serverSourceRoot);
const filePath = convertDebuggerPathToClient(urlObject, this._args.pathMappings);
// "Name" of the source and the actual file path
source = {name: path.basename(filePath), path: filePath};
}
Expand Down
148 changes: 94 additions & 54 deletions src/test/paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,37 +23,57 @@ describe('paths', () => {
});
});
describe('with source mapping', () => {
// unix to unix
it('should convert a unix path to a unix URI', () => {
const localSourceRoot = '/home/felix/myproject';
const serverSourceRoot = '/var/www';
assert.equal(
convertClientPathToDebugger('/home/felix/myproject/test.php', localSourceRoot, serverSourceRoot),
'file:///var/www/test.php'
);
// site
assert.equal(convertClientPathToDebugger('/home/felix/mysite/site.php', {
'/var/www': '/home/felix/mysite',
'/app': '/home/felix/mysource'
}), 'file:///var/www/site.php');
// source
assert.equal(convertClientPathToDebugger('/home/felix/mysource/source.php', {
'/var/www': '/home/felix/mysite',
'/app': '/home/felix/mysource'
}), 'file:///app/source.php');
});
// unix to windows
it('should convert a unix path to a windows URI', () => {
const localSourceRoot = '/home/felix/myproject';
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
assert.equal(
convertClientPathToDebugger('/home/felix/myproject/test.php', localSourceRoot, serverSourceRoot),
'file:///C:/Program%20Files/Apache/2.4/htdocs/test.php'
);
// site
assert.equal(convertClientPathToDebugger('/home/felix/mysite/site.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
'C:\\Program Files\\MySource': '/home/felix/mysource'
}), 'file:///C:/Program%20Files/Apache/2.4/htdocs/site.php');
// source
assert.equal(convertClientPathToDebugger('/home/felix/mysource/source.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
'C:\\Program Files\\MySource': '/home/felix/mysource'
}), 'file:///C:/Program%20Files/MySource/source.php');
});
it('should convert a windows path to a unix URI', () => {
const localSourceRoot = 'C:\\Users\\felix\\myproject';
const serverSourceRoot = '/var/www';
assert.equal(
convertClientPathToDebugger('C:\\Users\\felix\\myproject\\test.php', localSourceRoot, serverSourceRoot),
'file:///var/www/test.php'
);
// windows to unix
(process.platform === 'win32' ? it : it.skip)('should convert a windows path to a unix URI', () => {
// site
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysite\\site.php', {
'/var/www': 'C:\\Users\\felix\\mysite',
'/app': 'C:\\Users\\felix\\mysource'
}), 'file:///var/www/site.php');
// source
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysource\\source.php', {
'/var/www': 'C:\\Users\\felix\\mysite',
'/app': 'C:\\Users\\felix\\mysource'
}), 'file:///app/source.php');
});
it('should convert a windows path to a windows URI', () => {
const localSourceRoot = 'C:\\Users\\felix\\myproject';
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
assert.equal(
convertClientPathToDebugger('C:\\Users\\felix\\myproject\\test.php', localSourceRoot, serverSourceRoot),
'file:///C:/Program%20Files/Apache/2.4/htdocs/test.php'
);
// windows to windows
(process.platform === 'win32' ? it : it.skip)('should convert a windows path to a windows URI', () => {
// site
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysite\\site.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
}), 'file:///C:/Program%20Files/Apache/2.4/htdocs/site.php');
// source
assert.equal(convertClientPathToDebugger('C:\\Users\\felix\\mysource\\source.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
}), 'file:///C:/Program%20Files/MySource/source.php');
});
});
});
Expand All @@ -73,37 +93,57 @@ describe('paths', () => {
});
});
describe('with source mapping', () => {
(process.platform !== 'win32' ? it : it.skip)('should convert a unix URI to a unix path', () => {
const localSourceRoot = '/home/felix/myproject';
const serverSourceRoot = '/var/www';
assert.equal(
convertDebuggerPathToClient('file:///var/www/test.php', localSourceRoot, serverSourceRoot),
'/home/felix/myproject/test.php'
);
// unix to unix
(process.platform !== 'win32' ? it : it.skip)('should map unix uris to unix paths', () => {
// site
assert.equal(convertDebuggerPathToClient('file:///var/www/site.php', {
'/var/www': '/home/felix/mysite',
'/app': '/home/felix/mysource'
}), '/home/felix/mysite/site.php');
// source
assert.equal(convertDebuggerPathToClient('file:///app/source.php', {
'/var/www': '/home/felix/mysite',
'/app': '/home/felix/mysource'
}), '/home/felix/mysource/source.php');
});
(process.platform !== 'win32' ? it : it.skip)('should convert a windows URI to a unix path', () => {
const localSourceRoot = '/home/felix/myproject';
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
assert.equal(
convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/test.php', localSourceRoot, serverSourceRoot),
'/home/felix/myproject/test.php'
);
// unix to windows
(process.platform === 'win32' ? it : it.skip)('should map unix uris to windows paths', () => {
// site
assert.equal(convertDebuggerPathToClient('file:///var/www/site.php', {
'/var/www': 'C:\\Users\\felix\\mysite',
'/app': 'C:\\Users\\felix\\mysource'
}), 'C:\\Users\\felix\\mysite\\site.php');
// source
assert.equal(convertDebuggerPathToClient('file:///app/source.php', {
'/var/www': 'C:\\Users\\felix\\mysite',
'/app': 'C:\\Users\\felix\\mysource'
}), 'C:\\Users\\felix\\mysource\\source.php');
});
(process.platform === 'win32' ? it : it.skip)('should convert a unix URI to a windows path', () => {
const localSourceRoot = 'C:\\Users\\felix\\myproject';
const serverSourceRoot = '/var/www';
assert.equal(
convertDebuggerPathToClient('file:///var/www/test.php', localSourceRoot, serverSourceRoot),
'C:\\Users\\felix\\myproject\\test.php'
);
// windows to unix
(process.platform !== 'win32' ? it : it.skip)('should map windows uris to unix paths', () => {
// site
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/site.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
'C:\\Program Files\\MySource': '/home/felix/mysource'
}), '/home/felix/mysite/site.php');
// source
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/MySource/source.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': '/home/felix/mysite',
'C:\\Program Files\\MySource': '/home/felix/mysource'
}), '/home/felix/mysource/source.php');
});
(process.platform === 'win32' ? it : it.skip)('should convert a windows URI to a windows path', () => {
const localSourceRoot = 'C:\\Users\\felix\\myproject';
const serverSourceRoot = 'C:\\Program Files\\Apache\\2.4\\htdocs';
assert.equal(
convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/test.php', localSourceRoot, serverSourceRoot),
'C:\\Users\\felix\\myproject\\test.php'
);
// windows to windows
(process.platform === 'win32' ? it : it.skip)('should map windows uris to windows paths', () => {
// site
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/Apache/2.4/htdocs/site.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
}), 'C:\\Users\\felix\\mysite\\site.php');
// source
assert.equal(convertDebuggerPathToClient('file:///C:/Program%20Files/MySource/source.php', {
'C:\\Program Files\\Apache\\2.4\\htdocs': 'C:\\Users\\felix\\mysite',
'C:\\Program Files\\MySource': 'C:\\Users\\felix\\mysource'
}), 'C:\\Users\\felix\\mysource\\source.php');
});
});
});
Expand Down